Lottery Again - Web/Crypto
Challenge: Lottery Again
Author: Shotokhan
Description: ECB Cut and paste for PHP app's tokens
CTF: starCTF2021
Category: Web/Crypto
Writeup
There is a website which lets you register and gives you 300$ of charge. We got the source code, which is PHP.
With this charge, you can bet at a “lottery”: each ticket costs 100$ and will give you back a random value between 0 and 99.
The ticket is a JSON like this:
{"lottery":"cf4cfb25-8168-49db-a32f-4bf80e5bc785","user":"b6174052-f23a-4dbf-937d-fed3288b8de3","coin":1}
but it is encrypted.
Endpoints:
- The server gives you the ticket with the “buy” endpoint and decrease your coin amount by 100$.
- With the “charge” endpoint, you can use the ticket to increase your amount by the value specified in the ticket.
- You can know that amount using the “info” endpoint. We can’t use info as an oracle because from the source code we can see that, after decrypting, it tries to decode the result as JSON.
- We can get the flag using the “flag” endpoint if we manage to have 9999$ on our account.
From source code we see that tickets are encrypted using AES-256 bit in ECB mode.
Before trying an ECB penguin attack, we tried to double spend tickets. From the source code we see that a ticket is marked as used in the DB after spent.
Therefore, the only thing we can try is a race condition; we wrote a Python script for that, but it didn’t work because there are multiple checks.
So let’s go back to ECB penguin. Since AES is using a 256 bit key, we have blocks of 32 characters.
The idea is the following:
from the source code we see that the application doesn’t store any constraint between the lottery UUID and the user UUID in the DB; it trusts the lottery ticket, because it is encrypted with a key that only the server should know.
We can register as many users as we want, and this means that we can get as many tickets we want with valid UUID, each one with a random amount between 0 and 99.
If we manage to forge a ticket binding the lottery UUID and the UUID related to the user we want to make rich, then we can forge many tickets and reach the amount required to get the flag.
Let’s split a ticket in blocks.
['{"lottery":"cf4cfb25-8168-49db-a', '32f-4bf80e5bc785","user":"b61740', '52-f23a-4dbf-937d-fed3288b8de3",', '"coin":1}']
We could try to change only the 3rd block, but we should use users with the same first 6 characters of the UUID; this is not practicable.
Let’s remember that we’re dealing with PHP. If we make an associative array in PHP like this:
$d = ['lottery'=> 123, 'user'=> 1, 'user'=> 2];
echo $d['user'];
We will have 2 as result: it means that in an associative array in PHP, if we assign multiple times to the same key, it won’t raise an error, it will give reason to the last assignment instead.
Then, given a lottery ticket for the user we want to make rich, call it R, and call R_i the block in position i of that ticket; call A_i the block in position i of a ticket obtained with an auxiliary user.
We can forge the scam ticket as the following ECB cut and paste:
A_0 || A_1 || R_1 || R_2 || A_3
, where || is the concatenation.
Here is an example of what we can get:
{"lottery":"cf4cfb25-8168-49db-a32f-4bf80e5bc785","user":"b6174032f-4bf80e5bc785","user":"b6174052-f23a-4dbf-937d-fed3288b8de3","coin":1}
Notice that we didn’t change the coin amount: this is because the server checks it.
Now, all we have to do is to make a script:
- register an user to make rich
- login as that user (we will get an api token, each other operation requires us to send the api token)
- obtain a lottery ticket to get the ECB blocks
then, as long as we don’t have the required amount, we do the following:
- Register an auxiliary user
- login as that user
- for 3 times use ‘buy’ and ‘info’ endpoints (we need the info to know the ticket’s coin amount, to handle a sum variable for the coins we accumulated so far)
- forge 3 tickets as described above for the target user and use them (charge) with the api token of the target user.
And that’s all.