上个月,微软针对两个 SharePoint 的远程代码执行 (RCE) 漏洞发布了修复补丁。在 SharePoint 应用池的上下文和 SharePoint 的服务器账户中,攻击者可以发送一个特殊构造的请求来执行攻击者的代码,这两个漏洞都由 Markus Wulftange 上报给了 ZDI 组织,并且获得了漏洞编号为 CVE-2019-0604。
在找寻新漏洞的时候,一个常用的策略是自下而上。可以先通过找一段可能有漏洞的代码,并且追踪它所有与它相关的数据流。
其中,使用 XmlSerilizer 来进行反序列化就是一段很有可能包含漏洞的代码。通常来说,它是一个相对安全的序列化器,因为它要求数据流必须使用期望的类型,并且调用者无法在流中指定不能出现在预期类型的对象图中的任意类型。但是,如果预期的类型也能被控制,那么它是可利用的。
为了分析 SharePoint 2016,dnSpy可以用来实现对 .NET 应用程序的反编译和调试。因此,在将 dnSpy 附加到 IIS 的 worker 进程 (w3wp.exe) 后,这个进行运行了 SharePoint 2016,就能分析 XmlSerializer(Type) 的构造函数的用法了。现在,我们需要分析每一个调用 XmlSerializer(Type) 构造函数的地方,并且检查期望的类型是否是可变的(可变的是指的不像在 new XmlSerializer(typeof(DummyType)) 中那样硬编码)以及能否控制这个类型。
首先看到的一个调用 XmlSerializer(Type) 的地方是 Microsoft.SharePoint.dll 中的Microsoft.SharePoint.BusinessData.Infrastructure.EntityInstanceIdEncoder.DecodeEntityInstanceId(string)。相同的类型也同样出现在 Microsoft.SharePoint.Portal.dll 的Microsoft.Office.Server.ApplicationRegistry.Infrastructure中。下面,我先先看看 Microsoft.SharePoint.dll 中的代码。
在这里,typeName 和 text 都是由该方法的参数 encodeId 生成,typeName 用来指定期望的类型,而 text 用来进行反序列化。
这看起来不错,只要传递的参数能被我们控制,我们就能控制其反序列化的过程。
跟踪数据流
下一步是要找一下是否有一个地方可以从外面进行初始化,并且还可以指定其参数。
如果你对 ASP.NET 熟悉,这里面有一些方法你应该会看着很眼熟,比如 Page_Load(object, EventArgs) 和 OnLoad(EventArgs)。它们在整个的 ASP.NET 的生命周期中一直被调用,它们的类型是在 System.Web.UI.Page 中定义的。 System.Web.UI.Page 是 aspx 文件类型的基类,并且,一共有三个类型与aspx文件有关:
· Microsoft.SharePoint.ApplicationPages.ActionRedirectPage:
/_layouts/15/ActionRedirect.aspx
· Microsoft.SharePoint.ApplicationPages.DownloadExternalData:
/_layouts/15/downloadexternaldata.aspx
· Microsoft.SharePoint.Portal.WebControls.ProfileRedirect:
/_layouts/15/TenantProfileAdmin/profileredirect.aspx
尽管在这三种情况下,参数值都来自于 HTTP 请求,但它来自 URL 的请求字符串。可能会有一个问题,因为十六进制编码会将长度乘以4,因此会变得非常长,并超过HTTP请求的限制。
通过进一步的分析之后,列表中的最后一个方法,可能会是一个比较好的选择。
在这里,PickerEntity 方法中传入 PickerEntity 参数的属性 Key 被用来作为 EntityInstanceIdEncoder.DecodeEntityInstanceId(string) 的参数。它被 EntityEditor.Validate() 函数调用,它会迭代验证每一个存储在 EntityEditor.Entities 属性中的条目。
EntityEditor.Validate() 函数又被 EntityEditor.LoadPostData(string, NameValueCollection) 调用, 它实现了 System.Web.UI.IPostBackDataHandler.LoadPostData(string, NameValueCollection) 方法。
因此,当向 ItemPicker 控件发出 post back 请求时,该方法将自动被调用。调用图如下:
类型的层级结构为:
验证数据流
现在,我们已经有一个方法从 ItemPicker 调用到 EntityInstanceIdEncoder.DecodeEntityInstanceId(string) 了,但是目前还不清楚 PickerEntity 的 Key 属性是否也能被控制。
EntityEditor.Entities 属性是由 m_listOrder 生成,该字段只会在两个位置进行分配:实例化的过程中与 EntityEditor.Validate() 函数中。
在 EntityEditor.Validate() 函数中,它获取私有变量 m_listOrderTemp 的值(图4 中的 597 行)。而 m_listOrderTemp 的值也只会在两个地方进行分配:实例化的过程与 EntityEditor.ParseSpanData(string) 函数中。EntityEditor.ParseSpanData(string) 函数被 EntityEditor.LoadPostData(string, NameValueCollection) 调用,其参数是 hiddenSpanData 属性的值(图 5 中的 707 行)。
接下来,我们需要看 EntityEditor.ParseSpanData(string) 对传入的数据做了什么操作,以及确认一下它的尾部是否是 PickerEntity 的 Key。我们将跳过它,因为EntityEditor.ParseSpanData(string) 的代码非常长。我们发现除非是 <SPAN> 和 <DIV> 标签,其他的尾部都会是 PickerEntity 的 Key,并将其加入到 m_listOrderTemp 列表。
因此,现在我们已经找到了一个数据流,它允许我们从 ItemPicker 的 post back 请求一直传递到 EntityInstanceIdEncoder.DecodeEntityInstanceId(string) 函数,同时它的输入也是可控的。剩下的就是找到那个web控件的实例。
找到攻击入口
ItemPicker Web 控件实际上从来没有在一个 .aspx 页面中使用过。但是看看它基类型的用法,EntityEditorWithPicker,说明在 /_layouts/15/Picker.aspx 应该有一个 Picker.aspx 文件使用了它。
该页面要求使用选择器对话框的类型通过 URL 的 PickerDialogType 参数的形式提供。在这里,可以使用以下两种 ItemPickerDialog 类型中的任何一种:
· Microsoft.SharePoint.WebControls.ItemPickerDialog in Microsoft.SharePoint.dll
· Microsoft.SharePoint.Portal.WebControls.ItemPickerDialog in Microsoft.SharePoint.Portal.dll
利用第一种 ItemPickerDialog 类型会显示下面的页面:
这里,底部的文本字段与 ItemPicker 关联。还有 HtmlInputHidden 的对应项,它的名称是ctl00$PlaceHolderDialogBodySection$ctl05$hiddenSpanData。
PoC
当表单提交 ctl00$PlaceHolderDialogBodySection$ctl05$hiddenSpanData 的值以 “__” 为开头时(类似于“_dummy”),
EntityInstanceIdEncoder.DecodeEntityInstanceId(string) 处的断点将显示以下情况:
在这里,函数的调用栈为:
而调用另外一种 ItemPickerDialog 类型时,函数调用栈只是在最上面的两个有所不同。
这表明 ctl00$PlaceHolderDialogBodySection$ctl05$hiddenSpanData 的数据最终出现在了 EntityInstanceIdEncoder.DecodeEntityInstanceId(string) 中。 剩下的只需要拷贝实例 ID 和构造一个 XmlSerializer 的 payload 就可以了。
发表评论
您还未登录,请先登录。
登录