# Race Condition Exploitation

> Exploit time-of-check to time-of-use (TOCTOU) flaws in web apps via concurrent HTTP requests to bypass auth, duplicate transactions, or escalate privileges.

- **URL:** https://hackindex.io/services/web/exploitation/race-condition-exploitation
- **Path:** HTTP/HTTPS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/web/exploitation.md)
- **Published:** 2026-09-11
- **Updated:** 2026-09-11

Race condition exploitation targets time-of-check to time-of-use (TOCTOU) gaps in web applications. When a server checks a condition and acts on it in separate steps without atomicity, concurrent requests can slip through between the check and the action. Common targets include coupon redemption, balance transfers, vote counting, rate-limited actions, and any endpoint where a value is read, validated, then updated. The key requirement is that the application processes requests sequentially within a single request but does not lock the shared resource across concurrent requests.

- **HTTP/2 single-packet attack** sends all requests in one TCP segment using Turbo Intruder's gate mechanism.
- **Parallel curl requests** fire concurrent background processes for quick testing without Burp.
- **Threaded Python scripts** give fine-grained timing control and response parsing.
- **Common targets** include coupon redemption, balance transfers, rate-limited actions, and file upload race windows.

The practical challenge is timing: requests need to arrive at the server within the same processing window. Network jitter makes this unreliable over the internet. HTTP/2 single-packet attacks and Burp Suite's parallel request grouping solve this by ensuring multiple requests land in the same TCP segment, removing network variance from the equation. Race conditions frequently appear in [business logic exploitation](https://hackindex.io/services/web/exploitation/business-logic-exploitation) scenarios like checkout flows and coupon systems.

## HTTP/2 Single-Packet Attack

HTTP/2 multiplexes requests over a single connection. By holding back the final DATA frames of multiple requests and releasing them together in one TCP packet, all requests arrive at the server within microseconds. This is the most reliable technique for exploiting narrow race windows. The server's TCP stack delivers the entire packet at once, so all requests enter the application layer before any response processing begins.

Turbo Intruder (Burp Suite extension) implements this as "single-packet attack" mode. Prepare the requests first, then release them simultaneously:

```python
def queueRequests(target, wordlists):
    engine = RequestEngine(
        endpoint=target.endpoint,
        concurrentConnections=1,
        engine=Engine.BURP2
    )

    # Queue all requests before sending
    for i in range(20):
        engine.queue(
            target.req,
            gate='race'
        )

    # Release all at once in a single packet
    engine.openGate('race')

def handleResponse(req, interesting):
    table.add(req)
```

The `gate` parameter holds requests in a buffer. `openGate` flushes them all into a single TCP segment. Use `Engine.BURP2` for HTTP/2 or `Engine.THREADED` with `concurrentConnections=30` for HTTP/1.1 targets that do not support HTTP/2.

## Race Condition with curl

For quick testing without Burp, fire parallel curl requests using background processes and `wait`:

```bash
for i in $(seq 1 20); do
  curl -sk http://$TARGET_IP:$PORT/api/redeem -X POST -d 'code=DISCOUNT50' -b 'session=abc123' &
done
wait
```

This is less precise than single-packet attacks because each curl spawns a separate TCP connection with its own handshake. On local networks and fast targets it works well enough. Over the internet, results are inconsistent. Use the HTTP/2 approach for reliable exploitation.

## Race Condition with Python

A threaded Python script gives more control over timing and lets you parse responses programmatically:

```python
import threading
import requests

url = f"http://$TARGET_IP:$PORT/api/transfer"
cookies = {"session": "abc123"}
data = {"amount": "1000", "to": "attacker"}
results = []

def send():
    r = requests.post(url, data=data, cookies=cookies, verify=False)
    results.append(r.status_code)

threads = [threading.Thread(target=send) for _ in range(20)]

for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"Success: {results.count(200)}, Failed: {results.count(400)}")
```

## Common Race Condition Targets

**Coupon/discount redemption:** Apply a single-use code multiple times before the server marks it as used. Send concurrent requests all carrying the same code. If the server checks `code.used == false` and sets `code.used = true` in separate database operations, multiple requests pass the check before any one marks it used.

**Balance/transfer operations:** Withdraw or transfer more than the balance allows. The server reads the balance, validates the amount, then deducts. Concurrent requests all read the original balance before any deduction commits.

**Rate-limited actions:** Bypass rate limits that increment a counter after processing. If the counter check and increment are not atomic, concurrent requests all see the pre-limit count.

**File upload with processing:** Upload a malicious file and access it before server-side validation or antivirus scanning completes. Upload and request the file URL simultaneously. If the server writes the file to disk before scanning, a request between write and scan (or delete) reaches the unscanned file.

## Confirming Exploitation

A successful race condition shows multiple success responses where only one should be possible. Compare the responses: if 5 out of 20 requests return a 200 with a success message for a single-use action, the race condition exists. For financial operations, check the resulting balance or transaction log to confirm the actual impact.

If all attempts fail, increase concurrency or switch to the single-packet technique. Some applications use database-level locking (SELECT FOR UPDATE, advisory locks) or application-level mutexes that prevent the race entirely. If the application uses a queue or serialized processing for the specific operation, the race window does not exist regardless of request timing.

## References

- [Smashing the state machine](https://portswigger.net/research/smashing-the-state-machine) - PortSwigger research on single-packet attacks for race condition exploitation.
