DOM-based XSS
DOM-based XSS occurs when attacker-controlled data reaches an unsafe DOM operation through client-side JavaScript. The vulnerability exists in the browser-side data flow rather than requiring the server to reflect or store the payload.
DOM-based XSS is a client-side injection vulnerability. A typical flow has an attacker-controlled source, such as `location.hash`, `location.search`, or `document.referrer`, followed by JavaScript processing and an unsafe DOM sink.
The key issue is how data is interpreted at the sink. Assigning attacker-controlled text to `innerHTML`, for example, causes the browser to parse the value as HTML. By contrast, assigning untrusted text to `textContent` treats it as text rather than markup.
A secure design traces untrusted data from its source to its destination and avoids HTML-interpreting sinks when markup is unnecessary. If HTML must be created dynamically, the application should use an appropriate trusted rendering mechanism or a carefully configured sanitizer rather than concatenating raw input into HTML.
DOM-based XSS can be present even when the server never receives the malicious value. For example, JavaScript can read a fragment from the URL and immediately insert it into the page, making the browser itself responsible for the vulnerable data flow.
For interviews, distinguish DOM-based XSS from reflected and stored XSS by identifying where the dangerous transformation occurs. The important question is whether client-side code takes attacker-controlled data and sends it to an unsafe DOM sink.
Key points
- Trace attacker-controlled sources to DOM sinks.
- `innerHTML` interprets strings as HTML; `textContent` does not.
- URL fragments such as `location.hash` can provide attacker-controlled input.
- The vulnerability can occur entirely in browser-side code.
- Prefer safe DOM APIs and trusted rendering or sanitization when HTML is genuinely required.
Interview questions
What is DOM-based XSS?
DOM-based XSS occurs when client-side JavaScript takes attacker-controlled data and places it into a dangerous DOM context. Common sources include URL components, while common sinks include HTML-parsing APIs. The defining feature is that the vulnerable transformation happens in browser-side code. It does not require the server to store or reflect the payload.
Why is `innerHTML` dangerous with untrusted input?
`innerHTML` treats its assigned string as HTML markup, so untrusted content can introduce elements or event-handler attributes that the browser interprets. If the value is attacker-controlled, this can create an XSS condition. For plain text, `textContent` is preferable because it does not parse the assigned value as HTML. If dynamic HTML is required, the application needs an appropriate trusted rendering or sanitization strategy.
How would you find a DOM XSS vulnerability during a code review?
I would identify sources of attacker-controlled data and then trace where those values flow through the application. I would pay particular attention to HTML-parsing or script-sensitive sinks and to transformations that may make a value dangerous again. I would also inspect whether framework-specific escaping guarantees actually apply to the destination context. Finally, I would verify the fix by testing the complete source-to-sink path.
What is the difference between DOM-based, reflected, and stored XSS?
The distinction is primarily about where attacker-controlled data travels and where the vulnerable interpretation occurs. Reflected XSS commonly involves the server putting request data into a response immediately, while stored XSS involves persistence before later rendering. DOM-based XSS is caused by browser-side JavaScript processing data and reaching an unsafe DOM sink. These categories can overlap in a real application, so the actual source-to-sink flow matters more than the label alone.
How would you fix a DOM XSS vulnerability?
First, I would determine whether the HTML interpretation is actually necessary. If only text is needed, I would replace the unsafe sink with an API such as `textContent` or an equivalent framework-safe mechanism. If dynamic HTML is required, I would use a well-defined trusted rendering path or an appropriately configured sanitizer and ensure the resulting value is safe for its exact context. I would also add regression tests covering the relevant source-to-sink path and review related sinks for the same pattern.