Securebank

Over the past few weeks, a friend pulled me into solving the security puzzles offered by CSCG 2022. It’s been a great fun, learning experience, and time sink. Now we’re done with the easier ones, we’ve set our sights on Securebank.

The challenge

We are provided with the code for a web banking application. It’s in php, but neither the server configuration nor docker setup is provided for experimentation. CSCG always provide some sort of a session – the essence of the challenge – and the one for Securebank results in a web site with a login form. There doesn’t seem to be a way to register new users.

The approach

The first step of the solution is to acquire the credentials of an existing user. From reading the php code, it becomes evident, that the configuration uses sqlite – a development db solution, without security guarantees – and the file path is available too. One can indeed access the db and read the user and the md5 hash of their password:

john 6579e96f76baa00787a28653876c6127

My buddy tells me md5 is reversible, so after a quick search, I find this chunk of wisdom. It advises me to just perform a Google search for the hash and see what comes up. Fair enough:

Google search results for md5 yield the answer

It seems like it’s a popular password and the reverse of the hash is indexed by Google. The password is “johndoe”. Attempting to log in with this password results in a success, and we are presented with the main part of the banking app:

The UI of Securebank after login

Going to the “Promotion” page, gives us this text: “If your savings account reaches 25 euro or more you receive a free gift. Visit this page after you have saved 25 euro to claim your gift.” Probably, this would be the flag we are trying to capture. (I didn’t mention this, but CSCG 2022 is a capture-the-flag (CTF) competition, which means you have to find a hidden message in each challenge.)

My part

My friend did all of the work mentioned so far, so it took me just a few minutes to repeat it and write it up. The next step is where they got stuck and where I come in, trying to crack the walnut.

Since this is a banking app, I expected there to be a mishandling of transactions from the beginning, and reading the code confirms this. Updating the account balance is done in three steps: 1. get the old balance, 2. do expensive work, 3. set the new balance. It is quite clear that as soon as two transactions are performed concurrently, the balance will be miscalculated, as long as the server can handle requests in parallel. This last part, however, does not seem to be certain.

Request automation

My friend also provided me with their python script that attempts to send multiple requests to the server in a short time frame. They successfully used it to beat a previous challenge, so it should be working. When they point it to Securebank, however, they don’t manage to affect the total balance in the user account.

So let’s add some debugging prints. We poked at the script together a little bit, adding various timings and delays, but it didn’t affect the result: the total balance was always consistent. So I added some prints to illuminate the moment after each request was sent, but before the response was received. Very quickly it became evident that each request was only being sent after the previous one has returned.

Could it be that the server is configured in such a way that there’s only one active request per session?

I created a second session by logging in from a private browser tab. After duplicating the python code with two different session IDs, I quickly managed to affect the total balance. This perhaps explains why all banking apps have the horrendous user experience of allowing a single session per user at a time. But surely, you can just write the code right, so you don’t have to sacrifice ergonomics, right?

Anyway, spamming the server with bigger amounts quickly gets us to 25 EUR. And the promotion coupon is indeed the flag that needs to be captured. Challenge solved!