Payback
Last updated
Last updated
We got ourself a premium flag shop.
The app consists of two parts - the frontend and the payment backend (front
and payment
). Interestingly the user accounts for the frontend and backend are separate!
When performing a transaction, a message is signed using ED25519 to prevent tampering. The message format is user{u}amount{amount}nonce{nonce}
. The amount is checked, and since our balance is 0, we can only use 0 for the amount.
The user is redirected to /callback
on the frontend application.
This message is then verified by the frontend.
Notice that it iterates through the list of GET query parameters and adds them to the message before verifying that the message is the same as the one generated above.
Then, request.args.get('amount')
is added to the user balance.
Well, what if there are two amount
arguments? Only the first occurrence is returned by request.args.get
(so the amount added to the user's balance is the first amount
argument), yet both occurrences are added to the message to be verified.
Since the message format is user{u}amount{amount}nonce{nonce}
we can simply create a user with the username FRONTEND_USERNAMEamount1337
. The resulting message is then userFRONTEND_USERNAMEamount1337amount0nonceNONCE
.
The server gives us the signature for this message.
Then, we perform parameter pollution on the frontend:
GET /callback?user=FRONTEND_USERNAME&amount=1337&amount=0&nonce=NONCE&sig=SIGNATURE
.
Due to the way the frontend processes the parameters, this will result in the exact same message as above being checked, although carrying a different meaning.
We have successfully added 1337 coins to our account!
The flag is CSR{sometimes_it's_really_hard_to_create_good_flags}