FAUST CTF 2021 - Pirate Birthday Planner writeup
Pirate birthday planner
The service allows you to create a secret party with a list of guests.
To see the party information you need the party id, a valid guest name and the random pin.
The flagIds available at https://2021.faustctf.net/competition/teams.json
are also the party ids where flags are stored.
To read the flag we need to bypass the checks of name and pin.
Vulnerability
The portion of code to check if a user is authorized in the party is done in this middleware
1 | app.use('/party/:partyId', async function (req, res, next) { |
The query is vulnerable to noSQL injection, but we need to set both session.user
and session.pin
to a js object controlled by us.
The session is stored in a cookie created with the cookie-session library and it needs a signature with a random key, so we should find a way to get it from the server.
When using the app in the intended way the browser sends the requested data with Content-Type: application/x-www-form-urlencoded
. However, the server also loads a middleware to interpret the application/json
content type.
Thanks to it we can send data as json, injecting objects inside the input parameters of the server.
Our first try was the /new
endpoint
1 | app.post('/party/:partyId/new', (req, res) => { |
Here we confirmed that it’s possible to set as a pin an object like {"$ne":"a"}
and bypass the check.
However, this endpoint calls the trim()
function on the user parameter making it unusable for the exploitation.
We need to set both session.user
and session.pin
to an object to bypass the checks.
Exploit
We used two sessions in parallel to get the exploit working, a legit session and a session for the payload.
First we create a party in the legit session with valid values.
Then, in the other session, we create a party sending the admin
parameter equal to the object {"$ne":"a"}
. The creation of the party will fail, but the session values are updated anyway, in this way we set session.user
to the bypass object.
However, we have a random value in session.pin
, but we can’t change it because the corresponding party doesn’t exists in the server.
To solve this problem we set the pin of the legit party equal to the one in the payload session.
With the user bypass and the right pin the payload session will pass the checks for the legit party.
In the last step we use the payload session to change again the pin of the legit party with a bypass object.
Now the payload session can bypass the checks for all the parties in the server, we just need to collect all the flags from the /detail
endpoint and reverse the xor.
Script
1 | from requests import Session |