⚠️ Local Lab Only: DVWA is a deliberately vulnerable web application for security training. Run it only on your own isolated machine or lab network. Never deploy it publicly. DOM XSS techniques demonstrated here apply only to systems you own or have explicit written authorisation to test.
DVWA XSS DOM lab 2026 — DOM-based XSS is the XSS variant that most beginners skip over because it requires understanding how JavaScript reads and modifies the page, not just how the server outputs HTML. That is exactly why it matters: DOM XSS bypasses server-side controls entirely. The payload never reaches the server. A WAF sitting in front of the application sees nothing suspicious. Server-side output encoding has no effect. The vulnerability exists entirely in the browser, in the JavaScript that reads a URL parameter and writes it to the DOM without checking what it contains. Lab 10 teaches you to find the source, trace it to the sink, and understand why the server cannot protect against something it never sees.
🎯 What You’ll Learn in Lab 10
Understand the source → sink data flow that defines DOM XSS
Identify dangerous DOM sinks using browser DevTools and page source review
Exploit DOM XSS at Low, Medium, and High security levels
Bypass script tag filters using event handler and HTML element payloads
Understand why DOM XSS is invisible to server-side WAF and output encoding
In Lab 9 you exploited Stored XSS to hijack sessions. Lab 10 completes the XSS trilogy with DOM-based XSS — the most technically distinct of the three. While reflected and stored XSS both involve the server reflecting or serving malicious content, DOM XSS is a client-side vulnerability processed entirely in the browser. Understanding all three is required for comprehensive web application security testing in the DVWA Lab Series.
DOM XSS Explained — Sources, Sinks, and Why the Server Never Sees It
Every DOM XSS vulnerability has two components: a source (where attacker-controlled data enters the JavaScript environment) and a sink (where that data is written to the DOM or executed). The most common source in DVWA’s DOM XSS module is document.location — the current page URL, which an attacker controls by crafting a malicious link. The sink is document.write() — a function that writes HTML directly to the page, including any script tags or event handlers in the string it receives.
document.write() inserts raw HTML — script tags and event handlers in the string execute
↓ Browser renders the string as HTML ↓
③ EXECUTION — Script runs in victim’s browser
alert(document.cookie) → session token exposed to attacker
Server never saw the payload — only the browser processed it
📸 DOM XSS data flow — attacker-controlled URL parameter (source) is read by JavaScript and passed to document.write() (sink) without sanitisation. The browser renders the injected HTML, executing the script. The server receives only the original page request — it never sees the payload.
Low Security — Direct URL Parameter to document.write()
⚡ EXERCISE 1 — DVWA (12 MIN)
Exploit DOM XSS at Low Security — Inject Script via URL Parameter
⏱️ Time: 12 minutes · DVWA at Low security · Browser DevTools open
# Step 5: Open DevTools → Console — observe the alert executing
# Step 6: Open DevTools → Elements — find the injected script in DOM
# Step 7: Confirm — check Network tab. Did the payload reach the server?
# Answer: No server request contains the script payload — it’s all client-side
✅ What you just learned: Step 7 is the most important observation. Open the Network tab in DevTools before navigating to the payload URL, then check what was sent to the server. The server request shows only the normal page load — the ?default= parameter value is processed by the browser’s JavaScript engine, not sent to the server for processing. This is what makes DOM XSS uniquely dangerous from a defence perspective: server-side WAFs, input validation middleware, and output encoding on the server all operate on data that travels through the HTTP request/response cycle. DOM XSS completely bypasses this cycle for the payload execution phase.
📸 Screenshot the alert executing AND the Network tab showing no server-side payload. Share in #dvwa-labs on Discord.
Medium Security — Escaping the Select Element Context
Medium security strips <script> tags from the input using a simple string replacement. This prevents the direct script tag injection used in the Low security exploit. However, XSS does not require script tags — any HTML element that can execute JavaScript through event handlers is equally effective. The key to the Medium security bypass is understanding the injection context: the value is being inserted inside a <select> element’s option list. To break out of this context and inject event handlers, we close the select element first.
⚡ EXERCISE 2 — DVWA (12 MIN)
Bypass Medium Security DOM XSS Filter — Event Handler Injection
⏱️ Time: 12 minutes · DVWA at Medium security
MEDIUM SECURITY — CONTEXT ESCAPE + EVENT HANDLER
# Set DVWA to Medium security
# View Source to understand what filter is applied
# Step: Navigate to the payload URL and observe alert executing
# Then: DevTools → Elements — inspect the DOM to see injected element
✅ What you just learned: The context escape technique — closing the current HTML element before injecting — is a fundamental XSS bypass pattern. Understanding the injection context (where in the HTML your input lands) determines the payload structure. Inside a select option, <script> tags are prevented by the filter but the img onerror handler is not filtered. The filter only removes the substring “script” — it has no concept of HTML element context or event handler attributes. In the Elements panel after injection, you will see the <img> element with the onerror attribute sitting adjacent to the closed </select> — exactly as the payload intended.
📸 Screenshot the DOM Elements panel showing the injected img element after the closed select. Share in #dvwa-labs on Discord.
High Security — Whitelist Enforcement
High security implements a whitelist approach rather than a blacklist. Instead of trying to strip known-bad patterns (which attackers always find ways around), the server validates that the default parameter value is one of a specific list of acceptable language names. Any value not on the whitelist is replaced with a safe default. This closes both the Low security script injection and the Medium security event handler bypass — an attacker cannot inject arbitrary HTML because any value that does not match the whitelist is rejected before it reaches the DOM.
⚡ EXERCISE 3 — DVWA SOURCE ANALYSIS (10 MIN)
Analyse High Security Source and Understand the Hash Fragment Bypass Consideration
⏱️ Time: 10 minutes · DVWA at High security · View Source
HIGH SECURITY — SOURCE ANALYSIS + HASH FRAGMENT
# View Source at High security — observe the whitelist
// High PHP source:
if (stripos ($default, “<script”) !== false) {
header (“location: ?default=English”);
exit;
}
# The client-side JavaScript at High security uses the hash fragment
# View page source in browser → find the JavaScript block
// The JS reads: document.location.href (includes the # fragment)
// BUT: PHP only validates the ?default= query parameter
// The # fragment is NEVER sent to the server
# High security bypass: inject via URL fragment (not query param)
# The browser JavaScript reads the FULL href including the #
# The script tag after # reaches document.write()
# Verify: does the alert execute? Check the Network tab
# Network tab shows: GET /dvwa/vulnerabilities/xss_d/?default=English
# The #<script> payload is NEVER sent to server — fragment stays in browser
# This demonstrates the fundamental DOM XSS challenge for server-side defences
✅ What you just learned: The High security hash fragment bypass illustrates the fundamental DOM XSS defence challenge with perfect clarity. The server correctly validates the query parameter — it only allows “English” and similar whitelisted values. But the JavaScript reads the entire URL including the fragment (#), which the browser never sends to the server. The PHP validation never sees the payload. The WAF never sees the payload. The server access logs show nothing suspicious. Only the browser’s JavaScript engine processes the fragment — and if that JavaScript writes it to document.write() without checking the fragment content, the XSS executes despite server-side validation appearing to work correctly. This is why DOM XSS requires client-side defences.
📸 Screenshot the Network tab showing the server received only “?default=English” while the alert still executed from the fragment. Share in #dvwa-labs on Discord. Tag #domxss2026
DOM XSS in Real Applications — Where to Look
DOM XSS is common in modern single-page applications (SPAs) built with React, Angular, and Vue — not because these frameworks are insecure, but because developers sometimes use dangerous patterns: React’s dangerouslySetInnerHTML, Angular’s bypassSecurityTrustHtml, and jQuery’s $() when passed an HTML string from a URL parameter. In bug bounty, check every URL parameter that appears to control page content or navigation state, every hash fragment that affects page rendering, and any JavaScript that uses eval() with URL-derived data.
🧠 QUICK CHECK — Lab 10
A web application reads the URL fragment (#section=intro) using JavaScript and writes it to the page with innerHTML. You inject #section=<img src=x onerror=alert(1)> and the alert executes. Why can’t this attack be prevented by a server-side WAF?
📋 Lab 10 Key Takeaways — DOM XSS Reference
Common sourcesdocument.location, document.URL, location.hash, location.search, document.referrer
Low payload?default=<script>alert(document.cookie)</script> — direct script injection
Medium bypass</option></select><img src=x onerror=alert(1)> — context escape + event handler
High bypass?default=English#<script>alert(1)</script> — hash fragment never reaches server
Key insightDOM XSS payload never reaches server — server-side controls cannot prevent it
Correct defenceClient-side sanitisation at the sink: DOMPurify before innerHTML, textContent instead of innerHTML
🏆 Mark Lab 10 as Complete
Lab 10 completes the XSS trilogy. You now understand all three XSS types: reflected (server reflects input), stored (server stores and serves input), and DOM-based (browser JavaScript processes input without server involvement). DOM XSS’s server-invisibility makes it the most technically interesting of the three and a consistent finding in modern web application testing.
❓ Frequently Asked Questions – DVWA XSS DOM Lab
What is DOM XSS and how is it different from reflected or stored XSS?
DOM XSS processes entirely in the browser — the payload never reaches the server. Reflected XSS requires the server to echo the payload in the response. Stored XSS requires the server to store and serve the payload. DOM XSS bypasses all server-side controls because the browser’s JavaScript processes the attacker input directly from the URL or other client-accessible sources.
What are sources and sinks in DOM XSS?
Source = attacker-controllable JavaScript property (document.URL, location.hash, document.referrer, window.name). Sink = dangerous function that executes or renders the source data (document.write, innerHTML, eval, location.href). DOM XSS flows from source to sink without sanitisation.
Why does DOM XSS bypass WAF and server-side controls?
The payload is processed by the browser’s JavaScript engine, not the server. URL fragments are never sent to the server at all. Even query parameters, if processed only client-side, may not trigger server-side detection. Only client-side sanitisation (DOMPurify, textContent instead of innerHTML) prevents DOM XSS.
How do I find DOM XSS vulnerabilities in real applications?
Search JavaScript for dangerous sinks (document.write, innerHTML, eval) and trace back to sources. Test URL parameters and hash fragments. Use Burp Suite’s DOM Invader extension for automated detection. Review SPAs for dangerous patterns: React dangerouslySetInnerHTML, Angular bypassSecurityTrustHtml, jQuery $(htmlString).
What comes after DVWA Lab 10?
Lab 11: DVWA Blind SQL Injection — Boolean and Time-Based extraction. After completing the three XSS labs, the series advances to more sophisticated SQL injection techniques where results are not directly visible.
← Previous Lab
Lab 9: DVWA XSS Stored 2026
Next Lab →
Lab 11: DVWA Blind SQL Injection 2026
📚 Further Reading
DVWA XSS Reflected Lab 2026— Lab 8 covers the server-reflected XSS variant — understanding reflected XSS provides the baseline comparison that makes DOM XSS’s client-side nature distinctive.
DVWA XSS Stored Lab 2026— Lab 9 covers persistent XSS stored in the database — the most dangerous XSS type by impact, since it affects every user who loads the compromised page.
XSS Complete Guide— The complete XSS category covering all three variants, CSP bypass techniques, XSS-to-account-takeover chains, and real bug bounty XSS findings with PoC structure.
PortSwigger DOM XSS Labs— PortSwigger Web Academy’s DOM XSS module with interactive labs covering all common source-sink combinations including jQuery, AngularJS, and modern framework DOM XSS patterns.
DOMPurify — Client-Side Sanitisation Library— The standard client-side HTML sanitisation library that correctly prevents DOM XSS by stripping dangerous HTML before it reaches any sink — the correct technical remediation recommendation.
ME
Mr Elite
Owner, SecurityElites.com
The DOM XSS finding that made me appreciate the server-invisibility problem most vividly came from a financial services SPA built in React. The application had a comprehensive WAF, server-side input validation on every API endpoint, and output encoding throughout the server-rendered components. The security review had been completed three months earlier with no XSS findings. The DOM XSS was in a client-side routing component that read the URL hash for navigation state and used dangerouslySetInnerHTML to render the component title — a React prop that explicitly bypasses React’s automatic XSS protection. The WAF had never seen a suspicious request. The server logs showed nothing. The previous security review had focused entirely on server-side rendering. The payload was in the hash fragment and never touched any server-controlled component. It was found manually, reading the React component source in DevTools, following the data from window.location.hash to the dangerouslySetInnerHTML prop. Sometimes the most important security skill is reading other people’s JavaScript.
Leave a Reply