DOM-Based XSS — document.write via location.search¶
| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Vulnerability | DOM-Based Cross-Site Scripting (XSS) |
| Difficulty | Apprentice |
| Source | location.search (URL query string) |
| Sink | document.write() |
| Goal | Break out of the attribute context and execute JavaScript |
What is the DOM?¶
DOM stands for Document Object Model — it is the tree structure that represents an HTML document in memory. When a browser loads a page, it parses the HTML and builds this tree. Every HTML tag becomes a node:
html
├── head
│ └── title
└── body
├── h1
├── p
└── div
└── img
JavaScript can read and modify this tree at runtime using the DOM API — adding, removing, or changing elements and attributes without making a new HTTP request.
DOM-based XSS occurs when JavaScript reads data from an attacker-controlled source (like location.search) and writes it back to the DOM using a dangerous function (like document.write()). The vulnerability is entirely in the client-side code — the server may never see the malicious payload in a dangerous form.
Phase 1 — Reconnaissance¶
We find a blog with a search bar. Searching for any value puts it in the URL:
/?search=search
Phase 2 — Testing Basic Injection¶
<script>alert(0)</script>
No alert. Inspecting the HTML in DevTools revealed why — the input landed inside an img tag attribute, not as free HTML:
<img src="/resources/images/tracker.gif?searchTerms=<script>alert(0)</script>">
The < and > were HTML-encoded inside the attribute value — the script tag is treated as literal text. The payload is trapped inside a string. The server returned a safe response; the vulnerability lies elsewhere.
Phase 3 — Identifying the Sink¶
The application is running JavaScript like this:
document.write('<img src="/resources/images/tracker.gif?searchTerms=' + location.search + '">');
Our input is concatenated directly into the src attribute of an img tag via document.write(). The source is location.search — the URL query string. The sink is document.write() — a function that writes raw HTML to the page.
To execute JavaScript we need to break out of the attribute context first, then inject script as free HTML.
Phase 4 — Breaking Out of the Attribute Context¶
"><script>alert("hola")</script>
"— closes thesrcattribute value>— closes the<img>tag<script>alert("hola")</script>— payload injected as free HTML
The resulting DOM:
<img src="/resources/images/tracker.gif?searchTerms=">
<script>alert("hola")</script>
Alert fired. Lab solved.