XS-Leaks in CTFd: CSS Flag Exfiltration

x32x01
  • by x32x01 ||
A powerful XS-Leak vulnerability was discovered in CTFd versions below 3.7.2, allowing attackers to leak secret CTF flags from admin accounts - without using XSS.
This wasn’t a classic injection flaw.
This was browser side-channel exploitation at its finest. 💀

If you’re serious about bug bounty, web security, or advanced exploitation techniques, this is the kind of vulnerability that separates beginners from real researchers.

What Are XS-Leaks? (Cross-Site Leaks) 🔍​

XS-Leaks don’t directly steal response data.
Instead, they abuse tiny side effects caused by cross-origin requests.
Even though the Same-Origin Policy (SOP) prevents attackers from reading responses directly, browsers still expose small signals like:
  • HTTP status code differences (200 vs 404)
  • Page size variations
  • Frame count differences
  • Timing discrepancies
  • History state changes
  • CSS rendering differences like :visited
Each signal alone seems harmless.
Combined? They become a data exfiltration channel.
That’s the power of XS-Leaks.



The Target: Admin Flag Submissions in CTFd 🎯​

In vulnerable CTFd versions, admins could search flag submissions using:
Code:
GET /admin/submissions?q=FLAG_VALUE
Here’s where things broke:
  • ✅ If a matching flag existed → 200 OK
  • ❌ If no match existed → 404 Not Found (on specific paginated pages)
That difference became an oracle.

An oracle in security terms means:
A behavior difference that reveals whether a guess is correct.
And that’s exactly what happened.



Why Traditional XS-Leak Techniques Failed 🚧​

At first glance, the attack shouldn’t have worked.
There were protections in place:
  • SameSite=Lax cookies
  • Framing protections
  • Cross-origin restrictions
But there was a critical bypass.

If the page was opened in a top-level navigation context, cookies were still sent.
That reopened the attack surface.
This is where understanding browser behavior becomes a real advantage.



Step 1: Turning Search into a Flag Oracle 🧪​

The attacker brute-forced the flag one character at a time.
Example:
Code:
/admin/submissions?q=FLAG{A
/admin/submissions?q=FLAG{B
/admin/submissions?q=FLAG{C
If the prefix was correct → 200 OK
If incorrect → 404 Not Found
Now the challenge becomes:
How do you detect that difference from another origin?
That’s where things get creative.



Step 2: Abusing Browser History 📜​

Browsers store successful page loads (200 OK) in history.
But failed loads (404) may not be stored the same way.
This created a new side channel:
  • 200 → Stored in history
  • 404 → Not stored
Now the attacker just needed a way to detect whether a URL was visited.
And CSS provided the answer.



Step 3: Leaking Data Using CSS :visited 🎨​

Browsers allow styling of visited links:
Code:
a:visited {
  color: red;
}
Modern browsers block JavaScript from directly reading computed styles.
But attackers can still:
  • Generate many candidate links
  • Only one becomes :visited
  • Detect layout/rendering differences
  • Infer which character is correct
Example attack structure:
HTML:
<a href="https://target.com/admin/submissions?q=FLAG{A">A</a>
<a href="https://target.com/admin/submissions?q=FLAG{B">B</a>
<a href="https://target.com/admin/submissions?q=FLAG{C">C</a>
If one URL was previously loaded successfully (200), it becomes :visited.
That leaks one character.
Repeat the process.
Flag extracted.
No XSS required. 😈



Fully Automated XS-Leak (No User Click Needed) 🤯​

The exploit evolved further.
Instead of tricking the victim into clicking links, attackers used:
  • CSS layout differences
  • Rendering measurements
  • Timing side channels
Example concept:
JavaScript:
const start = performance.now();
document.body.offsetHeight;
const end = performance.now();
console.log(end - start);
Subtle rendering differences reveal which link was visited.
That made the attack:
  • Stealthy
  • Automated
  • Scalable
This is next-level web exploitation.



Root Cause Analysis 🛠️​

The vulnerability happened because of three things combined:
  1. Secret-dependent HTTP status codes
  2. Browser history behavior
  3. CSS :visited rendering differences
Individually harmless.
Together? Critical data leak.
This is why logic flaws often become high-impact vulnerabilities.



The Fix in CTFd 3.7.2 🛡️​

The patch included:
  • Removing 404 behavior in pagination (error_out=False)
  • Ensuring consistent HTTP response codes
  • Adding Cross-Origin-Opener-Policy headers
  • Reducing cross-window references
By normalizing responses, the oracle was eliminated.
No signal → No leak.



Why This Matters for Bug Bounty Hunters 🎯​

If you’re serious about bug bounty and penetration testing, here’s the takeaway:
Modern exploitation is no longer just:
  • XSS
  • SQL Injection
  • Command Injection
Now it’s about:
  • Side channels
  • Browser internals
  • Logic flaws
  • Subtle behavioral differences
Understanding browser security models gives you a massive advantage.
XS-Leaks are advanced skills - but they’re incredibly powerful.



Key Security Lessons 🔥​

Never return secret-dependent status codes.
Keep responses uniform in:
  • Size
  • Timing
  • Status
  • Structure
Use modern headers:
  • COOP
  • COEP
  • CORP
And most importantly:
Understand how browsers actually work.
They leak more than you think. 💀
 
Related Threads
x32x01
Replies
0
Views
84
x32x01
x32x01
x32x01
Replies
0
Views
507
x32x01
x32x01
x32x01
Replies
0
Views
1K
x32x01
x32x01
x32x01
Replies
0
Views
1K
x32x01
x32x01
x32x01
Replies
0
Views
1K
x32x01
x32x01
TAGs: Tags
browser history leak bug bounty techniques css visited attack ctfd vulnerability flag exfiltration http status oracle same origin policy side channel attack web security research xs leaks
Register & Login Faster
Forgot your password?
Forum Statistics
Threads
727
Messages
732
Members
70
Latest Member
blak_hat
Back
Top