Replay Attack
debt(d8/e5/b5/t7)
Closest to 'silent in production' (d8), slightly better than d9 because semgrep patterns can flag HMAC-signed requests missing timestamp/nonce fields, but detection_hints says automated:no — missing replay protection produces no errors, tests pass, and attacks leave no obvious trace.
Closest to 'touches multiple files / significant refactor in one component' (e5). Quick_fix requires adding nonce+timestamp to signed requests, server-side validation, and Redis-backed nonce storage with TTL — that's a coordinated change across signing, verification, and storage layers, not a one-liner.
Closest to 'persistent productivity tax' (b5). Once the signed-request protocol exists with nonce/timestamp rules, every new signed endpoint and every webhook integration must conform; applies_to web+api means broad reach across authenticated surfaces.
Closest to 'serious trap' (t7). The misconception field is explicit: developers believe HTTPS prevents replay because 'it's encrypted' — this contradicts intuition about TLS protection and is the canonical wrong mental model, making the obvious reasoning ('we use HTTPS, we're safe') wrong.
Also Known As
TL;DR
Explanation
Replay attacks occur when legitimate authentication messages or transactions are captured and retransmitted. Common examples include replaying captured session cookies, API request signatures without timestamps, or SMS OTP codes before they expire. Defences include: short-lived tokens with nonces, timestamps validated within a narrow window, HMAC signatures that include a timestamp and nonce, and one-time tokens invalidated on first use.
Common Misconception
Why It Matters
Common Mistakes
- Not including a timestamp in signed requests — a captured request is valid indefinitely.
- Not including a nonce — the same request can be submitted multiple times.
- Long timestamp windows (±1 hour) — captured requests valid for 2 hours.
- Not storing used nonces to reject duplicates — the nonce check is pointless without server-side tracking.
Code Examples
// Signed request without timestamp or nonce — replayable:
$signature = hash_hmac('sha256', $payload, $secret);
// Attacker captures payload + signature
// Replays it 1000 times — all accepted
// Replay-protected:
$data = $payload . '|' . time() . '|' . bin2hex(random_bytes(16));
$signature = hash_hmac('sha256', $data, $secret);
// Server: reject if timestamp > 5min old, reject if nonce seen before
// Timestamp + nonce — narrow replay window
\$timestamp = time();
\$nonce = bin2hex(random_bytes(16));
\$payload = json_encode(['action'=>'transfer','amount'=>100,'ts'=>\$timestamp,'nonce'=>\$nonce]);
\$signature = hash_hmac('sha256', \$payload, \$secret);
// Server verification:
\$ts = \$data['ts'] ?? 0;
if (abs(time() - \$ts) > 300) abort(400); // reject if older than 5 min
if (\$cache->has('nonce:' . \$data['nonce'])) abort(400); // already used
\$cache->set('nonce:' . \$data['nonce'], 1, 300); // mark used
if (!hash_equals(\$signature, \$receivedSig)) abort(401);