Cross-site scripting (or XSS) is one of the most common attack vectors exploited by hackers to steal sensitive data and exploit users on your platform.
You're probably here because your app was found to have a DOM-Based XSS vulnerability during an automated web app security test or a manual penetration test. I'm going to show you how you can fix this vulnerability across different components of your tech stack.
What is DOM-based Cross-Site Scripting?
DOM-based Cross-Site Scripting (XSS) is a type of XSS attack that occurs when an attacker manipulates the Document Object Model (DOM) in a user’s browser.
It is also known as Type-0 XSS and is a client-side vulnerability.
DOM-based XSS allows an attacker to inject malicious JavaScript code into a web page, which can lead to unauthorized actions, data theft, and compromised user sessions.
DOM-based XSS can be considered as a moderate to severe vulnerability depending on the context of application
How DOM-based XSS Attack Works
These attacks typically follow a process where an input from client side to the user (source) goes to an execution point (sink).
The source is the data that the attacker can control, and the sink is the DOM object or function that allows the attacker controlled data through JavaScript code execution or HTML rendering.
The attacker can steal the user’s cookies or change the behavior of the page on the web application by executing execute malicious javascript or javascript library.
The logic behind DOM XSS is that an input from the server side the user goes to an execution point, allowing the server side the attacker to execute malicious code.
Understanding the Document Object Model (DOM)
The Document Object Model (DOM) is a programming interface that gives developers the ability to access and manipulate the document (web page).
The DOM represents a web page’s structure, defining the basic properties and behavior of each HTML element.
The DOM helps scripts dynamically access and modify the page’s content, structure, and style.
The DOM is also what makes DOM-based XSS possible.
Sources and Sinks in DOM-based XSS Vulnerability
A source is a JavaScript property that contains data that an attacker could potentially control, such as document.URL, document.referrer, or location.
A sink is a DOM object or function that allows JavaScript code execution or HTML rendering, such as eval, setTimeout, or document.write.
Popular sources include document.URL, document.documentURI, location.href, and window.name.
Popular sinks include document.write, (element).innerHTML, eval, and setTimeout.
Consequences of DOM XSS Attacks
DOM-based XSS vulnerabilities are not very common, but the consequences of a successful attack can be dire.
A black-hat hacker could perform actions such as stealing sensitive information or taking control of the user’s session.
The actual impact of an XSS attack generally depends on the nature of the application, its functionality and data, and the status of the compromised user.
XSS attacks can have significant consequences, including data theft, unauthorized actions, and compromised user sessions.
Preventing DOM-based XSS Vulnerabilities
The best way to avoid DOM-based XSS vulnerabilities is to use a safe sink, such as innerText or textContent.
Avoid using untrusted data in sinks such as eval or execScript.
Use typical XSS protection techniques, such as filtering and escaping, applied to JavaScript.
Effectively preventing XSS vulnerabilities is likely to involve a combination of measures, including input validation, output encoding, and content security policy.
Here are some user input sanitization examples to prevent Dom-based XSS in different languages.
Preventing DOM-Based XSS in Java
public class InputSanitizer {
public static String sanitize(String input) {
if (input == null) {
return "";
}
// Remove dangerous characters and patterns
return input.replaceAll("[<>\"'&]", "")
.replaceAll("javascript:", "")
.replaceAll("on\\w+\\s*=", ""); // Remove event handlers
}
}
Preventing DOM-Based XSS in Django/Flask
Install bleach:
pip install bleach
Always sanitize user input
Use Django's built-in template escaping
For JavaScript context, use the
|escapejs
filterEnable CSRF protection in forms
Keep Django updated
from django.utils.html import escape
from bleach import clean
class XSSSanitizer:
@staticmethod
def sanitize_input(content):
if not isinstance(content, str):
return ""
# Allow only specific HTML tags
ALLOWED_TAGS = ['p', 'b', 'i', 'u']
ALLOWED_ATTRIBUTES = {'a': ['href']}
# Clean HTML and escape special characters
return clean(
content,
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES,
strip=True
)
# Usage in views
def secure_view(request):
user_input = request.POST.get('content', '')
safe_content = XSSSanitizer.sanitize_input(user_input)
# In templates
"""
{% autoescape on %}
{{ content|escape }}
{% endautoescape %}
<script>
const userContent = "{{ content|escapejs }}";
</script>
Preventing DOM-Based XSS in .Net
public static class XssSanitizer
{
public static string Sanitize(string input)
{
if (string.IsNullOrEmpty(input))
return string.Empty;
// HTML encode the input
var encoded = HttpUtility.HtmlEncode(input);
// Remove potentially dangerous patterns
return encoded
.Replace("javascript:", "")
.Replace("<script", "")
.Replace("onclick", "")
.Replace("onerror", "")
.Replace("onload", "");
}
}
// Usage
// In Controller/Razor Pages
string userInput = Request.Form["userInput"];
string safeInput = XssSanitizer.Sanitize(userInput);
// In Razor View
@Html.Raw(safeInput)
For built-in protection, use the @Html.Encode() helper or @ syntax in Razor views, which automatically encodes output.
Remember: For complex HTML content, consider using a library like HtmlSanitizer: Install-Package HtmlSanitizer
Preventing DOM-Based XSS in PHP
function sanitizeInput($input) {
if (!is_string($input)) {
return '';
}
// Remove dangerous characters and encode HTML
$clean = strip_tags($input);
$clean = htmlspecialchars($clean, ENT_QUOTES, 'UTF-8');
// Remove potentially dangerous patterns
$clean = str_replace(['javascript:', 'onclick=', 'onerror='], '', $clean);
return $clean;
}
// Usage
$userInput = $_POST['user_input'] ?? '';
$safeInput = sanitizeInput($userInput);
// When outputting in HTML
echo $safeInput;
// For JavaScript context
echo json_encode($safeInput, JSON_HEX_TAG | JSON_HEX_QUOT);
Preventing DOM-Based XSS in Ruby On Rails
# application_helper.rb
module ApplicationHelper
def sanitize_user_input(input)
return '' if input.nil?
# Basic sanitization
sanitized = strip_tags(input)
sanitized = sanitize(sanitized, tags: %w(p b i u), attributes: %w(class))
sanitized = h(sanitized) # alias for html_escape
# Remove dangerous patterns
sanitized.gsub!(/javascript:|on\w+=|data:/i, '')
sanitized
end
end
# In controller
def create
@content = sanitize_user_input(params[:content])
end
# In view
<%= sanitize_user_input(@content) %>
# For JavaScript context
<script>
const userContent = '<%= j(@content) %>'; # j is alias for escape_javascript
</script>
Ruby's advantage over other frameworks in preventing XSS
Rails auto-escapes content in views by default.
Use raw or html_safe only when absolutely necessary and always sanitize user input before storage.
Preventing DOM-Based XSS in pure Javascript
const sanitizeInput = (unsafe) => {
if (typeof unsafe !== 'string') return '';
return unsafe
.replace(/[&<>"'/]/g, char => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
})[char])
.replace(/javascript:/gi, '')
.replace(/on\w+=/gi, '');
};
// Usage examples:
// For HTML content
const userInput = document.querySelector('#input').value;
const safeContent = sanitizeInput(userInput);
element.textContent = safeContent; // Safe
// element.innerHTML = safeContent; // Avoid using innerHTML
// For attributes
element.setAttribute('data-content', safeContent);
// For URLs
const url = new URL(userInput, window.location.origin);
if (url.protocol.startsWith('http')) {
link.href = url.toString();
}
-
Use textContent over innerHTML
Use DOMPurify library for complex HTML sanitization
Always validate URLs
Never pass user input directly to eval() or innerHTML
Testing for DOM-based Cross-Site Scripting Vulnerabilities
Testing for DOM XSS is not as easy as testing for Reflected and Stored XSS, as the values will appear in the DOM, not in the source code.
To test for DOM XSS, use developer tools to inspect the DOM and look for sinks and sources.
Manual penetration testing or professional DAST scanners that use an embedded browser engine are needed to test for DOM-based XSS.
The vast majority of XSS vulnerabilities can be found quickly and reliably using Burp Suite’s web vulnerability scanner.
Real-World Impact of DOM XSS vulnerability
A basic example of a classic DOM XSS vulnerability involves malicious script for customizing a web page based on user input. The user input is encoded in the URL and the script code used directly on the resulting page. An attacker can then manipulate the URL payload to include malicious JavaScript code.
DOM-based XSS attacks have led to serious security breaches at major organizations, enabling attackers to make web application and steal sensitive user data like session cookies and credentials by manipulating client-side JavaScript to execute malicious code in users' browsers.
A notable example occurred in 2018 when a DOM XSS vulnerability in Google's search page allowed attackers to call malicious script to steal users' search history and Gmail credentials, demonstrating how even well-resourced companies can be impacted by this attack vector.
Twitter (now X) faced a significant DOM-based XSS vulnerability in their Tweet Deck service in late 2023, where attackers could inject a malicious payload of JavaScript through manipulated URL parameters, potentially affecting millions of users who could have had their authentication tokens stolen and accounts compromised. This incident serves as another stark reminder of web security vulnerability and how DOM XSS vulnerabilities in high-traffic web applications can create widespread security risks.
Best Practices for web developers
Use a safe sink, such as
innerText
ortextContent
, to avoid using untrusted data in sinks such as eval or execScript.Use best-practice XSS protection techniques, such as filtering and escaping, applied to JavaScript.
Effectively preventing XSS vulnerabilities is likely to involve a combination of measures, including input validation, output encoding, and content security policy.
Keep your third-party applications updated and pay attention to the code of your web applications.
How to test for DOM based XSS on your app?
You can protect your application from DOM based XSS attacks by running regular vulnerability scans with a user-friendly web application vulnerability scanning tool, like Cyber Chief, which helps you find critical vulnerabilities like DOM based XSS attacks and thousands more.
Watch the Cyber Chief on-demand demo to see not only how it can help to keep attackers out, but also to see how you can ensure that you ship every release with zero known vulnerabilities.
Cyber Chief has been built to integrate with the workflows of high-growth SaaS teams and that's why it offers:
Results from scanning your application for the presence of OWASP Top 10 + SANS CWE 25 + thousands of other vulnerabilities.
A detailed description of the vulnerabilities found.
A risk level for each vulnerability, so you know which ones to fix first.
Best-practice fixes for each vulnerability, including code snippets where relevant.
On-Demand Security Coaching from our application security experts to help you patch vulnerabilities in hours, not days.
Click the green button below to see how Cyber Chief works.