How would you feel if you could get free money in your bank account?
Race conditions are usually found in binaries, and are rarely something that a pentester thinks whenever they are performing an assessment. The usual bugs that one finds are more like XSSs, SQLis, business flow bypasses, etc.
This story however happened during a three week assessment of the home banking application of an important bank, which of course shall remain nameless, lest they come from my head.
Home bankings are always crucial applications, and any critical findings means that the application owner and their developers are in a lot of trouble, which usually means that these applications are heavily tested and mature.
This means that after three weeks of pentesting, I was pretty much out of ideas. So, I had to scrape the bottom of the barrel for findings, and suddenly I found gold.
For obvious reasons, I’m going to show this in an application I created myself, to illustrate the vulnerability:

The transfer process on a big bank is usually very complex, however this application emulates the steps which have to be followed during a transfer:
- Check if the parameters received are valid.

This includes checking if the parameters are present, that they are of the expected type; in this case the “from” and the “to” should be integers and the amount should be a float. And finally, check if the amount is greater than 0. This is very important, otherwise we could transfer a negative amount, and “create” infinite money.
2. Check if the “from” account exists and get its balance

This queries the database for the account, and returns its balance or null on error:

3. Check if the account has sufficient money for the transfer. Here is where the vulnerability lies. This is called a TOCTOU (Time Of Check to Time Of Use), in which the balance check operation and the transfer operation are not performed atomically (ie. in one single operation).

The sleep is to simulate the delay between the balance check and the transfer, otherwise it is difficult to trigger the vulnerability locally.

So, how do we exploit this?
For simplicity, we are going to use Burp’s intruder.
First, we capture a normal transfer request:

Then we send it to Intruder and we add a bogus header, so we can control the number of requests that we are going to send:

Then we configure the number of threads and the number of requests:


And finally, we send the requests:

And, once we go back to see the accounts balance:

Here’s a video of the full attack in action:

So, what happened here?
The main problem with this code, is that the operation “check balance” and the operation “transfer” are not performed atomically, which means that they are not performed as one single operation.
Usually this is not a problem, however if we send multiple threads, the following can happen:
Step 1:
Thread 1: Has the source account enough balance? Yes. Proceed to the next step.
Thread 2: Has the source account enough balance? Yes. Proceed to the next step.
Thread 3: Has the source account enough balance? Yes. Proceed to the next step.
Step 2:
Thread 1: Transfer: Add 1000 to the target account and subtract 1000 from the source account.
Thread 2: Transfer: Add 1000 to the target account and subtract 1000 from the source account.
Thread 3: Transfer: Add 1000 to the target account and subtract 1000 from the source account.
This leads to the target account being added 3000 and the source account being subtracted 3000.
Final notes: This type of vulnerability is usually not tested in applications, because it’s mostly associated with binary vulnerabilities. However, we always have to analyze and learn about the business processes, to see if a race condition could provide leverage for an attacker.
References:
- https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
- https://en.wikipedia.org/wiki/Race_condition
Code:
2 thoughts on “Web application race conditions: It’s not just for binaries.”