XSS Security Lab

DOM-based XSS Vulnerability Demonstration

Test Your Knowledge

VULNERABLE - DOM-based XSS

Lab #3: DOM-based XSS via URL Fragment

Client-side XSS through document.write injection

This lab demonstrates a DOM-based XSS vulnerability where JavaScript reads the # (hash/fragment) from the URL and writes it directly to the page using document.write() without sanitization. The payload never touches the server — it's purely client-side.

Goal: Execute JavaScript by crafting a malicious URL fragment (#) parameter.
No fragment detected. Add # to the URL (e.g., #Hello)
URL Fragment (#): (none)
Current URL:
Hints (click to expand)
  • The vulnerable sink is: document.write(userInput)
  • DOM-based XSS happens entirely in the browser
  • Try: #<img src=x onerror=alert(1)>
  • Try: #<svg onload=alert(document.doindex)>
  • The fragment (#) is never sent to the server!

Solution & Explanation

Working Payloads:

  • #<img src=x onerror=alert('XSS')> — Image error event
  • #<svg onload=alert(1)> — SVG onload
  • #<body onload=alert(document.cookie)> — Body onload
  • #<input onfocus=alert(1) autofocus> — Input autofocus

Why it works: The app reads window.location.hash and passes it directly to document.write(). The browser executes any HTML/JS injected in the fragment.

Vulnerable Code:

// VULNERABLE: Reads fragment and writes to DOM unsafely
function displayFragment() {
    const fragment = window.location.hash.substring(1);
    const decoded = decodeURIComponent(fragment);
    
    if (decoded) {
        // DANGER: document.write with user input!
        document.getElementById('output').innerHTML = decoded;
        // OR: document.write(decoded);
    }
}

Fix (Secure Code):

// FIX 1: Use textContent instead of innerHTML
document.getElementById('output').textContent = decoded;

// FIX 2: Escape HTML before inserting
function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
}
document.getElementById('output').innerHTML = escapeHtml(decoded);

// FIX 3: Avoid document.write entirely
// Never use document.write() with user input!

DOM-based XSS vs Other Types:

  • Reflected XSS: Payload in URL query string, reflected by server
  • Stored XSS: Payload saved in database, served to all users
  • DOM-based XSS: Payload in URL fragment (#), processed by client-side JS only