Timing Attack
debt(d5/e1/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm as tools, both of which are specialist static analysis tools that can detect `===` or `==` comparisons of tokens/HMACs in authentication contexts. This is not caught by a compiler or default linter, but is within reach of a configured SAST rule — matching d5 exactly.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix explicitly states: replace security-sensitive string comparisons with hash_equals($known, $user_input). This is a direct one-for-one function substitution at each call site, making it e1. The only caveat is 'all' comparisons must be found, but each fix individually is a single-line swap.
Closest to 'localised tax' (b3). The issue applies to authentication, CSRF, and HMAC comparison points — a bounded set of security-sensitive locations. It doesn't permeate every file or define the system's architecture, but developers working on auth/security components pay a recurring cost of remembering to use hash_equals everywhere. Slightly above b1 because it recurs across all auth-related code, but doesn't slow down unrelated workstreams.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field directly states that developers believe microsecond timing differences are too small to matter over the internet — this is the canonical wrong belief. The 'obvious' instinct is to use `===` (the idiomatic PHP equality operator) for comparing strings, which is exactly wrong for secrets. The statistical averaging technique that makes remote timing attacks viable contradicts most developers' intuition about network noise, making this a serious cognitive trap justifying t7.
Also Known As
TL;DR
Explanation
A timing attack exploits the fact that string comparison operators (===, strcmp) return early on the first mismatched character, so comparing a correct token takes slightly longer than an incorrect one. By making thousands of requests and measuring response times, an attacker can determine the correct token one character at a time. hash_equals() prevents this by always taking the same time regardless of where the strings differ. Always use it when comparing tokens, HMAC signatures, and CSRF values.
How It's Exploited
# Correct prefix → takes slightly longer → reveals chars one by one
# Requires many requests and statistical analysis but works against == comparison
Common Misconception
Why It Matters
Common Mistakes
- Using === to compare password hashes, HMACs, or tokens — exits early on first byte mismatch.
- Using strcmp() which also short-circuits and has additional issues with null bytes.
- Not using hash_equals() in PHP for all secret comparisons.
- Applying timing-safe comparison only to the final comparison but not to intermediate validation steps.
Avoid When
- Never use === or == to compare HMAC signatures, API tokens, or password hashes — both short-circuit on the first differing byte.
- Do not compare secrets with strcmp() or strncmp() — they are not constant-time.
When To Use
- Use hash_equals() for all security-sensitive string comparisons — it runs in constant time regardless of where strings differ.
- Use password_verify() for password checks — it uses constant-time comparison internally.
Code Examples
// === exits on first mismatch — reveals partial token via timing
if ($_GET['token'] === $secretToken) { grantAccess(); }
// hash_equals() — constant-time comparison regardless of where mismatch occurs
if (hash_equals($secretToken, $_GET['token'] ?? '')) {
grantAccess();
}
// For password verification — always use password_verify (also constant-time)
if (password_verify($input, $storedHash)) { /* authenticated */ }
// Timing-safe HMAC comparison for API signatures
$expected = hash_hmac('sha256', $requestBody, $secret);
$provided = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if (!hash_equals($expected, $provided)) { abort(401); }