Skip to main content
Offensive360
Home / Knowledge Base / Race Condition / TOCTOU
High CWE-362 A04:2021 Insecure Design

Race Condition / TOCTOU

Race conditions occur when application behavior depends on the timing of concurrent operations. TOCTOU flaws allow attackers to exploit the gap between check and use, leading to privilege escalation, double-spending, and data corruption.

Affects: C#JavaJavaScriptPythonGoPHP

What is a Race Condition?

A race condition occurs when the correctness of a program depends on the relative timing or ordering of concurrent events. In web applications, attackers can exploit race conditions by sending multiple simultaneous requests to trigger operations that should only execute once — such as redeeming a coupon, withdrawing funds, or consuming a one-time token.

Time-of-Check to Time-of-Use (TOCTOU) is a specific subtype: the application checks a condition (e.g., “does the user have sufficient balance?”), then — before acting on the result — the shared state changes. An attacker races multiple requests through the check simultaneously, all passing before any deduction is applied.

How exploitation works

A discount coupon redemption endpoint:

1. Check: Is coupon "SAVE10" valid and unused? → Yes
2. [GAP — attacker fires 10 simultaneous requests]
3. Mark coupon as used → only updates once
4. Apply discount → applied 10 times

All 10 concurrent requests pass step 1 before any of them execute step 3, allowing the coupon to be applied multiple times. Similar attacks are used against loyalty point systems, file-based locks, and limited-inventory purchases.

Vulnerable code examples

C# / ASP.NET Core — non-atomic check-and-update

// VULNERABLE: Read and update are separate, non-atomic operations
[HttpPost("redeem")]
public async Task<IActionResult> RedeemCoupon(string code, string userId)
{
    var coupon = await _db.Coupons.FindAsync(code);
    if (coupon == null || coupon.IsUsed) return BadRequest("Invalid coupon");

    // RACE: Another request can pass this check before this update executes
    coupon.IsUsed = true;
    coupon.UsedBy = userId;
    await _db.SaveChangesAsync();
    await ApplyDiscount(userId, coupon.Value);
    return Ok();
}

Node.js — filesystem TOCTOU

// VULNERABLE: Check then use — file could change between stat() and readFile()
async function processConfig(filePath) {
    const stat = await fs.stat(filePath);
    if (stat.size > MAX_SIZE) throw new Error('File too large');
    // RACE: attacker replaces file with symlink between stat and readFile
    const content = await fs.readFile(filePath, 'utf8');
    return parseConfig(content);
}

Secure code examples

C# — atomic database update

// SECURE: Single atomic UPDATE with WHERE condition prevents double redemption
[HttpPost("redeem")]
public async Task<IActionResult> RedeemCoupon(string code, string userId)
{
    // Atomic: only succeeds if IsUsed is currently false
    var updated = await _db.Database.ExecuteSqlRawAsync(
        "UPDATE Coupons SET IsUsed=1, UsedBy=@userId WHERE Code=@code AND IsUsed=0",
        new SqlParameter("@userId", userId),
        new SqlParameter("@code", code));

    if (updated == 0) return BadRequest("Coupon already used or invalid");
    await ApplyDiscount(userId, await GetCouponValue(code));
    return Ok();
}

Java — optimistic locking with database versioning

// SECURE: Optimistic locking — update fails if another transaction modified the record
@Entity
public class Coupon {
    @Version
    private long version; // JPA increments this on every update
    private boolean used;
    // ...
}

// Service: ObjectOptimisticLockingFailureException thrown on concurrent update
@Transactional
public void redeemCoupon(String code, String userId) {
    Coupon coupon = couponRepository.findByCode(code)
        .filter(c -> !c.isUsed())
        .orElseThrow(() -> new InvalidCouponException());
    coupon.setUsed(true);
    couponRepository.save(coupon); // Throws if version conflict
}

What Offensive360 detects

  • Non-atomic check-then-act patterns — Read-then-write sequences on shared state without locking or atomic operations
  • Missing database-level constraints — Unique constraints or conditional updates absent from critical resources
  • Filesystem TOCTOUstat()/exists() calls followed by open()/read() on user-influenced paths
  • Unprotected counters/balances — Increment/decrement operations on shared numeric values outside transactions
  • Missing idempotency controls — Payment or action endpoints lacking request deduplication keys

Remediation guidance

  1. Use atomic database operations — Perform check-and-update as a single SQL statement with a WHERE condition (e.g., UPDATE ... WHERE status='pending') and check the affected row count.

  2. Use optimistic or pessimistic locking — ORMs support row-level locking (SELECT FOR UPDATE) and version-based optimistic locking (@Version, rowversion).

  3. Apply unique database constraints — Enforce business rules at the database level so duplicate operations fail at the storage layer regardless of application logic.

  4. Implement idempotency keys — For payment and redemption endpoints, accept a client-supplied idempotency key and deduplicate requests at the application level.

  5. Use distributed locks for cross-service operations — When coordinating across microservices, use Redis SET NX or equivalent distributed locking primitives.

References

By Offensive360 Security Research Reviewed: March 2026

Detect Race Condition / TOCTOU automatically

Run Offensive360 SAST on your codebase to find this and 100+ other vulnerabilities.