The Day the House Lost: Exploiting a Race Condition to Print Infinite Money (Rainbet)
Introduction - Rainbet and Race Conditions
They say the house never loses, which, according to my recent trip to Vegas, seemed true. Security watches closely, slots are programmed to be addictive yet unfair. The whole place is designed to make a profit, and it does. That’s the house, but what if the house is online? That definitely changes things. The frontend interacts with the backend, and we may not be able to access the backend, but we can still tinker with what we can access. We can inspect the often obfuscated JavaScript and watch the HTTP requests being sent out. Those requests are what we leverage today to exploit the house and, yes, make money out of thin air.
The site I found this vulnerability on was Rainbet.com, a popular crypto based casino. Players can play a variety of games, some built by Rainbet, some outsourced. I noticed a feature I’ve seen popping up a lot in casino pentests lately: the vault. A user can deposit funds into the vault as a sort of safe spot, or withdraw them back to their balance. My immediate thought was could this be vulnerable to a race condition?
What is a Race Condition?
A race condition happens when two or more operations rely on shared state and the timing between them causes incorrect behavior. In web apps this often looks like a check-then-update pattern that isn’t executed atomically. If multiple requests interleave during the window between check and update, each can pass the check and then apply its own update, producing inconsistent or incorrect results.
Let’s look into a very classic example of a race condition, before we get into more advanced techniques. Imagine a redemption page. A user goes to redeem a $25 code. They send the request to redeem, but what if they didn’t actually send it? What if they just captured the POST request, used it to redeem and saved it. This is very easy to achieve using tools such as Burp Suite’s intercept option. Now, imagine we used a python script to send the request 100x concurrently, they would all send in parallel. What would be the outcome? Would it work as intended, and would the gift card be redeemed only once, or would something unexpected happen, would around 100x the balance be added to our account? Well, if there was a race condition, this is a very likely case. Feel free to look at the example script below.

Race Conditions aren’t always that simple though. Today, we will look in one of the other ways a race condition can impact a website. Enter: the multi-endpoint race condition. This name was dubbed by James Kettle, after he released research on different ways he managed to exploit web based race conditions. However, when dealing with multi-endpoint race conditions, we don’t just race two identical requests, we abuse two different endpoints. In the case I will show you today, it was /vault/deposit and /vault/withdraw. By sending multiple deposit requests concurrently alongside a withdrawal request, we could manipulate timing so the withdraw’s effect got effectively duplicated into our balance (the net effect being that the withdraw amount was added back to our balance, making money appear out of thin air).
Diving deeper
As mentioned above, this vulnerability is a multi-endpoint race condition in Rainbet’s vault service where concurrent operations on two endpoints (/vault/deposit and /vault/withdraw) these endpoints, could be interleaved in a way that caused the withdraw to be effectively duplicated back into the user’s balance. Based on my findings, the system processed state changes for deposits and withdrawals without sufficient cross-request atomicity or ordering guarantees, allowing timing to be manipulated so that a withdrawal’s effect was neutralized and its amount re-credited.
Now let’s dig into the details and I’ll explain how I was able to make this happen.
Firstly, the balance needs to be deposited. After that, a lesser amount needs to be transferred into the vault. For example, deposit $120 and transfer $50 into the vault, leaving $70 in the balance. Next we collect the requests.
POST /vault/deposit HTTP/2
Host: api.rainbet.com
and
POST /vault/withdraw HTTP/2
Host: api.rainbet.com
These requests can be grabbed by intercepting the HTTP traffic when withdrawing or depositing. Headers should be present as well, specifically a JWT Bearer token.
Now, for the actual body inside the POST request;
{
"amount": "20",
"language": "en",
"currency": "USD"
}
{
"amount": "35.47",
"language": "en",
"code": "2FACodeHere",
"currency": "USD"
}
In terms of exploiting this vulnerability. there are many options. However, for this scenario, I opted to use Burp Suite’s built in repeater feature. I grouped the deposit requests and sent them concurrently with one withdrawal request, which allowed me to trigger the race condition.

When the system processed deposit and withdrawal requests in parallel, the internal sequence of reads/writes to the user’s balance could be timed such that the withdrawal’s deduction happened against an out of date view or was later overwritten by a deposit related write.
The end result: the withdrawal amount was not subtracted (or was added back), so repeated attempts of this processes could inflate the balance, effectively generating infinite money.
Video PoC
Possible server-side causes
No cross-endpoint guarantees: The deposit and withdraw code both touched the same balance but weren’t wrapped in any transaction that would force them to run in a safe order relative to each other.
Balance updates weren’t atomic: Each handler would read the balance, calculate a new amount, and then write it back. If two requests happened at the same time, one update could overwrite the other.
Weak concurrency controls: There wasn’t effective locking around a single user’s account. Either there was no lock at all or it was too coarse to be useful, so multiple requests could slip through in parallel without being properly serialized.
Impact and final thoughts
The financial loss to Rainbet, in my opinion, could have been millions. Rainbet told me they monitor withdrawals carefully. However, an attacker could gamble and then withdraw methodically to avoid detection. This will allow the attacker to convert inflated balances into real funds. Monitoring alone may not catch sophisticated low and slow abuse.
After all this, I reported my findings to Rainbet. They quickly addressed the issue and awarded me a $5,000 USD bounty. I’m grateful for the reward, though in the wrong hands, as mentioned above I truly believe far more in damages, could have occurred. Nonetheless, I’m thankful for the bounty and had a lot of fun digging into Rainbet.