Cross-Site Leaks (XS-Leaks): Testing Modern Browser Side-Channel Vulnerabilities

Cross-Site Leaks (XS-Leaks) are a class of browser-based vulnerabilities that let an attacker infer information about another origin's content — not by directly reading it (the Same-Origin Policy blocks that), but by observing side effects: timing differences, error messages, cache behavior, frame count, navigation history, and a dozen other browser signals that leak information across the origin boundary.

XS-Leaks don't require a user to click anything malicious. The attacker hosts a page that the victim visits. While the victim's browser silently makes requests to authenticated targets on their behalf, the attacker's page measures responses — not the content of the response, but behavioral signals that correlate with the content. Does the request succeed? How long does it take? Does it redirect? Does the page contain frames? These signals, combined and observed at scale, reveal private information.

This is a fundamentally different threat model from traditional XSS or CSRF. You don't need code execution or a forged request. You need an attacker page, a victim who is logged in to the target application, and browser APIs that leak state across origins.

Why XS-Leaks Matter Now

XS-Leaks aren't new — the category has existed in some form since the mid-2000s. What's changed is the breadth of attack surface and the sophistication of documented techniques. The xsleaks.dev knowledge base catalogs dozens of distinct leak techniques, browser-specific variants, and mitigation vectors. Major tech companies including Google and Microsoft have paid significant bug bounties for XS-Leak findings.

For penetration testers and AppSec engineers, XS-Leaks represent a class that most automated scanners miss entirely — because the vulnerability lives in the interaction between browser features, application responses, and cross-origin behavior, not in a single vulnerable parameter.

Core Concepts

The Same-Origin Policy and Its Limits

The Same-Origin Policy (SOP) prevents page-a.example.com from directly reading the response body of a request to app.target.com. Your attacker page cannot read the HTML of a victim's authenticated session. But the SOP doesn't prevent:

  • Observing whether a cross-origin request succeeded or failed
  • Measuring the timing of cross-origin requests
  • Counting the number of frames in a cross-origin page
  • Detecting cache state of cross-origin resources
  • Observing navigation events and URL changes
  • Measuring postMessage traffic characteristics

Each of these is a potential information leak channel.

Oracles and State Inference

The core concept in XS-Leaks is the oracle — an observable signal that correlates with application state. A well-designed XS-Leak attack works by asking the oracle a question and inferring the answer from the observable difference.

Example: a search endpoint that returns 200 when results exist and 404 when they don't is an oracle. An attacker who knows this can ask "does user X have any transactions over €10,000?" by loading app.target.com/search?q=amount:>10000 in an iframe and observing the frame's load behavior. If the endpoint returns 404 for no results, the iframe may behave detectably differently than when it returns 200 with results.

Major XS-Leak Technique Categories

1. Error-Based Oracles

The most straightforward XS-Leak category. An attacker includes a cross-origin resource (image, script, stylesheet) in their page. If the resource loads successfully, one event fires; if it fails (401/403/404), a different event fires. The attacker observes which event occurred.

Example: User enumeration via avatar images

Scenario: app.target.com/users/{userId}/avatar returns 200 for existing users and 404 for non-existent users, without authentication.

Attack: An attacker page loads this URL as an <img> element and attaches onload and onerror event handlers. onload fires for existing users, onerror for non-existent ones.

Impact: User existence can be determined for any userId the attacker tries.

Example: Sensitive content detection via authentication state

Scenario: app.target.com/admin/dashboard returns 200 for admin users and 403 for regular users.

Attack: An attacker page embeds this URL in an iframe and observes whether a frame error or successful load event occurs. Determines if the victim has admin privileges.

Impact: Role enumeration without reading any content.

2. Timing Attacks

Response timing differences reveal information when application processing time correlates with data state. A database query that filters a large result set takes longer than one that returns no results. A search endpoint that processes complex queries takes longer than one that immediately returns "not found."

Timing measurements across origins aren't perfectly precise, but consistent timing differences of even 50–100ms can be statistically significant over multiple requests. Techniques include:

  • Network timing — measuring time from request initiation to onload event
  • Cache timing — measuring whether a resource is served from cache (fast) vs. origin (slow), inferring prior access
  • Connection pool exhaustion — saturating the victim's browser connection pool and measuring whether new requests to the target are queued (indicating the target is being accessed)

3. Frame Count Oracles

When a page is embedded in an iframe, the attacker can read iframe.contentWindow.frames.length — the number of nested frames inside the cross-origin page. This is one of the few properties not blocked by the SOP. If the target page's frame count varies based on the authenticated user's state (e.g., an admin panel adds a sidebar frame, or search results render in separate frames), the attacker can infer application state from the frame count.

4. Cache Probing

Browsers cache resources. An attacker can probe whether a specific resource is in the victim's browser cache by measuring load time — cached resources load in <1ms, uncached resources require a network round trip. This reveals whether the victim has recently visited specific URLs, which can correlate with application activity.

Cache probing became harder after browsers implemented cache partitioning (keying the cache by both origin and top-level site), but not all browsers implement it consistently, and timing differences in other layers (OS DNS cache, service worker caches) create residual channels.

5. Navigation and History Oracles

The window.history.length property tracks the number of entries in the browser's navigation history. If loading a cross-origin URL in a window changes history.length by a predictable amount based on the user's state (e.g., an authenticated session generates redirects, an unauthenticated one doesn't), the attacker can observe this.

Similarly, history.back() and history.forward() behavior across cross-origin navigations can leak redirect chain length, which correlates with application state.

6. postMessage Oracles

Applications that use postMessage for cross-window communication sometimes leak state through message content, timing, or the fact that a message was sent at all. If a legitimate integration causes a target application to emit a postMessage only when the user has a specific account state, an attacker who can receive that message (or observe that it wasn't sent) has an oracle.

7. Fetch Metadata and CORB Bypass Indicators

Browser-level mitigations like Cross-Origin Read Blocking (CORB) and Fetch Metadata headers generate observable signals when they trigger. An attacker can sometimes infer content type, authentication state, or response characteristics from whether CORB blocked a response vs. allowed it.

Testing for XS-Leaks

Reconnaissance: Identifying Oracles

Start by mapping which endpoints produce different observable behavior based on authenticated state or data content:

  • Endpoints that return 404 vs. 403 vs. 401 depending on user state
  • Search endpoints whose response time correlates with result count
  • Authenticated pages whose structure (frame count, redirect chain) depends on user role or data
  • APIs that use postMessage for cross-origin communication
  • Resources loaded conditionally based on user state (avatars, preference files, account-specific assets)

Testing Error-Based Leaks

<!-- Test page to check if a user exists via avatar endpoint -->
<script>
function probeUser(userId) {
    return new Promise((resolve) => {
        const img = new Image();
        img.onload = () => resolve({ userId, exists: true });
        img.onerror = () => resolve({ userId, exists: false });
        img.src = `https://target.example.com/users/${userId}/avatar`;
    });
}

// Probe a list of user IDs
Promise.all([1, 2, 3, 100, 9999].map(probeUser))
    .then(results => console.log(results));
</script>

Testing Timing-Based Leaks

<script>
async function timeCrossOriginRequest(url) {
    const start = performance.now();
    try {
        await fetch(url, { mode: 'no-cors', credentials: 'include' });
    } catch(e) {}
    return performance.now() - start;
}

// Run multiple samples and average to reduce noise
async function measureWithRetries(url, samples = 10) {
    const times = [];
    for (let i = 0; i < samples; i++) {
        times.push(await timeCrossOriginRequest(url));
        await new Promise(r => setTimeout(r, 50)); // small delay between samples
    }
    return times.reduce((a, b) => a + b, 0) / times.length;
}

// Compare timing for "exists" vs "doesn't exist" condition
const timeExists = await measureWithRetries('/api/search?q=known_term');
const timeEmpty = await measureWithRetries('/api/search?q=nonexistent_xyz123');
console.log(`Exists: ${timeExists}ms, Empty: ${timeEmpty}ms, Delta: ${timeExists - timeEmpty}ms`);
</script>

Testing Frame Count Oracles

<script>
function checkFrameCount(url) {
    return new Promise((resolve) => {
        const iframe = document.createElement('iframe');
        iframe.src = url;
        iframe.onload = () => {
            // Small delay for nested frames to load
            setTimeout(() => {
                resolve(iframe.contentWindow.frames.length);
                document.body.removeChild(iframe);
            }, 500);
        };
        document.body.appendChild(iframe);
    });
}

const frameCount = await checkFrameCount('https://target.example.com/dashboard');
// If admin users get a frame count of 2 and regular users get 1,
// this is an oracle for privilege level
console.log(`Frame count: ${frameCount}`);
</script>

Defenses Against XS-Leaks

Cross-Origin-Opener-Policy (COOP)

The Cross-Origin-Opener-Policy: same-origin header isolates your page into its own browsing context group, preventing cross-origin pages from getting a reference to your window. This blocks several attack vectors that rely on the attacker opening your application in a popup and inspecting window properties.

Cross-Origin-Opener-Policy: same-origin

Cross-Origin-Resource-Policy (CORP)

The Cross-Origin-Resource-Policy: same-origin header prevents your resources from being loaded by cross-origin pages. Applied to sensitive resources (user avatars, account-specific assets), this blocks error-based oracles that rely on loading your resources in an attacker page.

Cross-Origin-Resource-Policy: same-origin

Fetch Metadata Request Headers

Modern browsers send Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest, and Sec-Fetch-User headers with every request. Use these to implement a Resource Isolation Policy on the server: reject or return generic responses for cross-origin requests that shouldn't be loading your resources.

# Nginx: block cross-site resource loads for sensitive endpoints
if ($http_sec_fetch_site = "cross-site") {
    return 403;
}

SameSite Cookie Attributes

SameSite=Strict or SameSite=Lax cookies aren't sent with cross-site subresource requests. This means authenticated requests from an attacker page won't carry session credentials, eliminating the "authenticated state" component of many XS-Leak oracles. If your application's sensitive endpoints don't behave differently for unauthenticated requests, most user-state oracles disappear.

Consistent Response Shapes

Design APIs to return consistent status codes and response times regardless of authentication state. An endpoint that returns 404 for "not found" vs. 403 for "found but unauthorized" is leaking information. Return 404 in both cases (or a generic 403 that doesn't distinguish between "doesn't exist" and "you don't have access"). Similarly, design search endpoints to have consistent response timing regardless of result count by using fixed timeouts or result set caps.

Cross-Origin-Embedder-Policy (COEP)

Cross-Origin-Embedder-Policy: require-corp combined with COOP enables cross-origin isolation, which also enables high-resolution timers (SharedArrayBuffer) to be used by your page while removing them from potential attacker contexts. This is primarily for enabling features, but the isolation properties also reduce timing attack surface.

XS-Leaks in Penetration Test Scope

XS-Leaks are valid penetration test findings, but their severity is context-dependent. A frame count oracle that reveals whether a user has admin privileges is a medium-severity finding. A timing oracle that can reconstruct private search queries is high severity. An error-based oracle that confirms whether specific account numbers exist is high severity in a banking context, lower in a public directory application.

When reporting XS-Leak findings, document:

  • The specific oracle mechanism (error-based, timing, frame count, etc.)
  • The attacker-controlled page required to exploit it
  • What information an attacker can infer and its sensitivity
  • Whether exploitation requires a victim to visit the attacker's page
  • Which of the available mitigations addresses the specific technique

Testing reality: XS-Leak testing is largely manual. The attack surface requires application-specific knowledge — which endpoints return different status codes based on state, which have timing characteristics that correlate with data. Automated scanners that don't understand your application's business logic won't surface these findings. They belong in manual penetration test scope, not automated scanning scope.

Ironimo automates coverage of the OWASP Top 10 and common application-layer vulnerabilities, freeing your manual testing budget for complex findings like XS-Leaks that require application-specific context and human judgment.

Join the waitlist for early access.

Start free scan
← Back to blog