Why is this an issue?
DOM-based cross-site scripting (XSS) occurs in a web application when its client-side logic reads user-controllable data, such as the URL, and then
uses this data in dangerous functions defined by the browser, such as eval()
, without sanitizing it first.
When well-intentioned users open a link to a page vulnerable to DOM-based XSS, they are exposed to several attacks targeting their browsers.
What is the potential impact?
A well-intentioned user opens a malicious link that injects data into the web application. This data can be text, but also arbitrary code that can
be interpreted by the user’s browser, such as HTML, CSS, or JavaScript.
Below are some real-world scenarios that illustrate some impacts of an attacker exploiting this vulnerability.
Website defacement
An attacker can use the vulnerability to change the target web application’s content as they see fit. Therefore, they might replace the website’s
original content with inappropriate content, leading to brand and reputation damage for the web application owner. It could additionally be used in
phishing campaigns, leading to the potential loss of user credentials.
User impersonation
When a user is logged into a web application and opens a malicious link, the attacker can steal that user’s web session and carry out unauthorized
actions on their account. If the credentials of a privileged user (such as an administrator) are stolen, the attacker might be able to compromise all
of the web application’s data.
Theft of sensitive data
Cross-site scripting allows an attacker to extract the application data of any user that opens their malicious link. Depending on the application,
this can include sensitive data such as financial or health information. Furthermore, by injecting malicious code into the web application, it might
be possible to record keyboard activity (keylogger) or even request access to other devices, such as the camera or microphone.
Chaining XSS with other vulnerabilities
In many cases, bug hunters and attackers can use cross-site scripting vulnerabilities as a first step to exploit more dangerous
vulnerabilities.
For example, suppose that the admin control panel of a web application contains an SQL injection vulnerability. In this case, an attacker could
find an XSS vulnerability and send a malicious link to an administrator. Once the administrator opens the link, the SQL injection is exploited, giving
the attacker access to all user data stored in the web application.
How to fix it in DOM API
Code examples
The following code is vulnerable to DOM-based cross-site scripting because it uses unsanitized URL parameters to alter the DOM of its webpage.
Because the user input is not sanitized here and the used DOM property is vulnerable to XSS, it is possible to inject arbitrary code in the user’s
browser through this example.
Noncompliant code example
The Element.innerHTML
property is used to replace the
contents of the root
element with user-supplied contents. The innerHTML
property does not sanitize its input, thus allowing
for code injection.
const rootEl = document.getElementById('root');
const queryParams = new URLSearchParams(document.location.search);
const input = queryParams.get("input");
rootEl.innerHTML = input; // Noncompliant
Compliant solution
The HTMLElement.innerText
property does not
create DOM elements out of its input, rather treating its input as a string. This makes it a safe alternative to Element.innerHTML
depending on the use case.
const rootEl = document.getElementById('root');
const queryParams = new URLSearchParams(document.location.search);
const input = queryParams.get("input");
rootEl.innerText = input;
How does this work?
In general, one should limit the use of dangerous properties and methods, such as Element.innerHTML
or Document.write()
,
as there exist many ways for an attacker to exploit their usage. Instead, prefer the usage of safe alternatives such as
HTMLElement.innerText
or Node.textContent
. Furthermore, frameworks such as React or Vue.js will automatically escape
variables used in views, making it much harder to accidentally write vulnerable code.
If these options are not possible, sanitization of the attacker-controllable input should be preferred.
Sanitization of user-supplied data
By systematically encoding data that is written to the DOM, it is possible to prevent XSS attacks. In this case, the goal is to leave the data
intact from the end user’s point of view but make it uninterpretable by web browsers.
However, selecting an encoding that is guaranteed to be safe can be a complex task. XSS exploitation techniques vary depending on the HTML context
where malicious input is injected. As a result, a combination of HTML encoding, URL encoding and JavaScript escaping may be required, depending on the
context. OWASP’s DOM-based XSS Prevention Cheat
Sheet goes into more detail about the required sanitization.
Though browsers do not yet provide any direct API to do this sanitization, the DOMPurify library
offers extensive functionality to prevent XSS and has been tested by a large user base.
Pitfalls
The limits of validation
Validation of user inputs is a good practice to protect against various injection attacks. But for XSS, validation on its own is not the
recommended approach.
For example, filtering out user inputs based on a denylist will never fully prevent XSS vulnerabilities from being exploited. This practice is
sometimes used by web application firewalls. Time and time again, malicious users are able to find the exploitation payload that will defeat the
filters of these firewalls.
Another common approach is to parse HTML and strip sensitive HTML tags. Again, this denylist approach is vulnerable by design: maintaining a list
of sensitive HTML tags is very difficult in the long run.
Modification after sanitization
Caution should be taken if the user-supplied data is further modified after this data was sanitized. Doing so might void the
effects of sanitization and introduce new XSS vulnerabilities. In general, modification of this data should occur beforehand instead.
Going the extra mile
Content Security Policy
With a defense-in-depth security approach, a Content Security Policy (CSP) can
be added through the Content-Security-Policy
HTTP header, or using a <meta>
element. The CSP aims to mitigate XSS
attacks by instructing client browsers not to load data that does not meet the application’s security requirements.
Server administrators can define an allowlist of domains that contain valid scripts, which will prevent malicious scripts (not stored on one of
these domains) from being executed. If script execution is not needed on a certain webpage, it can also be blocked altogether.
Resources
Documentation
Articles & blog posts
Standards