DOM XSS in document.write Sink Using location.search
DOM-based Cross-Site Scripting (DOM XSS) is one of the most important vulnerability classes in modern web security because it occurs entirely on the client side. Unlike reflected or stored XSS, where the server plays a direct role in injecting malicious content into responses, DOM XSS happens when insecure JavaScript running in the browser processes user-controlled input in an unsafe way and inserts it into the page as executable code.
One of the most common and educational examples of this vulnerability is DOM XSS in a document.write sink using location.search as the source. This pattern is widely used in security labs because it clearly demonstrates how client-side JavaScript can transform simple URL input into executable JavaScript inside the browser.
Understanding this vulnerability requires breaking it down into three core components: source, sink, and execution context.
Understanding the Core Components
1. Source: location.search
The first component is the source of the data. In this case, it is location.search, which represents the query string in the URL.
For example:
Everything after the question mark is:
This value is:
-
Fully controlled by the user
-
Easily modified in the browser address bar
-
Commonly used in search functionality, filters, and tracking parameters
Because it is user-controlled, it is considered an untrusted input source. Any value placed here can be manipulated by an attacker to inject malicious payloads.
2. Sink: document.write()
The second component is the sink, which is where the vulnerability occurs.
document.write() is a JavaScript function that writes raw HTML directly into the document during page rendering. Unlike modern DOM APIs, it does not perform any encoding or sanitization. Instead, it treats everything passed into it as HTML markup.
For example:
This means whatever is in the URL is directly injected into the HTML structure of the page.
The critical issue here is that document.write() does not distinguish between:
-
Safe text
-
HTML elements
-
JavaScript code
Everything is treated as executable markup.
3. Execution Context: HTML Parsing
The final component is the execution context.
When document.write() injects content, the browser immediately parses it as HTML. This is important because HTML parsing rules determine how the browser interprets tags and scripts.
This means:
-
<h1> becomes a real HTML element
-
<b> changes formatting
-
<script> executes JavaScript immediately
-
Event handlers like
onclick become active behavior
This creates a direct execution path:
User input → JavaScript processing → HTML injection → Browser parsing → Code execution
Example Lab results after testing:


How the Vulnerability Works in Practice
To understand how DOM XSS occurs in this scenario, consider a typical vulnerable implementation:
If a user visits:
The output becomes:
At first glance, this appears safe. However, because there is no encoding or sanitization, an attacker can modify the input to include HTML or JavaScript.
Exploitation Scenario
An attacker can craft a malicious URL such as:
This gets processed by JavaScript and injected into the page:
When the browser parses this HTML, it encounters the <script> tag and executes it immediately. This results in JavaScript execution inside the context of the vulnerable site.
This is the core of DOM XSS exploitation: turning user-controlled input into executable code inside the browser.
Why This Happens
The vulnerability exists because of a combination of insecure design decisions:
-
Untrusted input (
location.search) is used directly
-
Dangerous sink (
document.write) is used
-
No output encoding is applied
-
The browser is allowed to interpret raw HTML
The main issue is that user input is treated as HTML code instead of plain text.
Payload Structure and Injection Logic
Exploitation often involves carefully structured payloads designed to break out of the existing context and inject executable code.
A common pattern looks like this:
This payload works in multiple steps:
-
" closes an attribute or string
-
> closes an HTML tag
-
<script> injects executable JavaScript
-
</script> terminates the script block
This structure allows the attacker to regain control over the HTML layout and insert arbitrary code.
Why document.write() Makes It Worse
document.write() is especially dangerous because it operates during the page parsing phase.
This means:
-
The DOM is still being constructed
-
HTML is not yet finalized
-
Scripts execute immediately as they are parsed
Unlike safer methods such as textContent, which treat input as text, document.write() directly modifies the document stream.
Because of this, any injected HTML is immediately processed by the browser.
Real-World Impact
Although this vulnerability is often demonstrated in lab environments, the same pattern appears in real-world applications, especially in:
-
Search result pages
-
Filtering and sorting parameters
-
Legacy JavaScript codebases
-
Analytics or tracking scripts
-
URL-based state management
The impact can be significant. Once exploited, DOM XSS can allow attackers to:
-
Execute arbitrary JavaScript in the victim’s browser
-
Steal session data if accessible
-
Perform phishing attacks inside trusted websites
-
Modify page content dynamically
-
Redirect users to malicious domains
-
Perform actions on behalf of the user
Because the script runs in the context of a trusted domain, it bypasses many user trust assumptions.
Why DOM XSS Is Hard to Detect
Unlike server-side XSS, DOM XSS does not always appear in server logs because:
-
The server may never see the malicious payload
-
The transformation happens entirely in the browser
-
JavaScript modifies the DOM after page load
This makes detection harder for traditional security tools like:
-
Web application firewalls
-
Backend logging systems
-
Server-side scanners
Instead, detection requires client-side analysis or manual code review.
Secure Coding Practices
To prevent this vulnerability, developers must avoid unsafe DOM manipulation patterns.
The correct approach is to ensure that user input is treated as data, not code.
Safe alternative:
This ensures:
-
HTML tags are not interpreted
-
JavaScript cannot execute
-
Input is rendered as plain text
Other best practices include:
-
Avoid using
document.write()
-
Use modern DOM APIs (
createElement, appendChild)
-
Apply context-aware output encoding
-
Implement Content Security Policy (CSP)
Conclusion
DOM XSS in document.write using location.search demonstrates a fundamental security flaw in client-side JavaScript applications. The vulnerability arises when untrusted URL parameters are directly passed into a dangerous DOM sink without proper encoding or validation.
By understanding the relationship between source (location.search), sink (document.write), and execution context (HTML parsing), it becomes clear how simple input can evolve into executable JavaScript.
A payload such as:
is enough to compromise the page when processed in an unsafe environment.
Ultimately, this vulnerability reinforces one of the most important principles in web security:
User input must never be treated as executable code, only as data to be safely rendered.
Comments