Timing Attacks
The response time tells you what the response body will not.
What leaks
| Difference | What it means |
|---|---|
| Login slow vs fast | User exists (bcrypt only runs on real accounts) |
| Reset slow vs fast | Email registered |
| Signup slow vs fast | Username taken |
| 404 slow vs fast | Route exists but is unauthorised |
| Request slow with an internal host | Blind SSRF, port open |
| Response slower per character | Blind injection, sleep() reached |
| Token compare slow | Non-constant-time comparison, byte-by-byte recovery |
Kill the noise first
Timing on the open internet is mostly jitter. Get the signal above it:
- Send both requests in the same packet (see Race Conditions), the single biggest improvement
- Compare a pair, not an absolute number
- 30+ samples, take the minimum, not the mean. The minimum is the least noisy estimator
- Warm the connection before measuring
Burp Repeater > Send group in parallel → both requests, one packet, comparable timingTurbo Intruder
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1,
engine=Engine.BURP2)
for word in open('/usr/share/wordlists/users.txt'):
engine.queue(target.req, word.rstrip())
def handleResponse(req, interesting):
if req.time > 400:
table.add(req)Scanner
Param Miner’s timing mode finds unkeyed inputs and hidden parameters purely by response time, no reflection needed.
Burp > Extensions > Param Miner > Guess headers / Guess params (timing)Server-side timing headers
Free measurements the app hands you, jitter removed:
Server-Timing: db;dur=53.2, cache;dur=0.4
X-Runtime: 0.104
X-Response-Time: 12msIf Server-Timing is exposed, a blind SSRF or blind SQLi becomes an accurate oracle.
Cross-site
Timing works from an attacker page against a logged-in victim:
fetch()withmode: no-corsand measure- Frame the target and time
onload - Cache probing: is this resource already in the victim’s HTTP cache?
- Connection pool exhaustion to time a cross-origin request
Reporting
“1.2s vs 0.3s” is not a report. Give the sample size, the two request variants, and the recovered data: a list of valid usernames or the extracted value.