DEV Community

Cover image for Part 6: Cross-Site Scripting (XSS) Series -DOM-based XSS – Understanding Client-Side Vulnerabilities
Trix Cyrus
Trix Cyrus

Posted on

Part 6: Cross-Site Scripting (XSS) Series -DOM-based XSS – Understanding Client-Side Vulnerabilities

Author: Trix Cyrus

Waymap Pentesting tool: Click Here
TrixSec Github: Click Here
TrixSec Telegram: Click Here


In this part of the series, we dive into DOM-based Cross-Site Scripting (DOM-based XSS)—a distinct and challenging type of XSS vulnerability. Unlike Stored or Reflected XSS, DOM-based XSS occurs entirely in the client-side environment, often through the misuse of JavaScript to process user-controlled inputs. This vulnerability is especially dangerous as it bypasses server-side protections, making it harder to detect and mitigate using traditional security measures.


What Makes DOM-based XSS Different?

In DOM-based XSS, the malicious payload is never sent to the server. Instead, the vulnerability exists in the browser’s Document Object Model (DOM). This makes it different from Stored and Reflected XSS, which involve injecting and reflecting data through the server.

Here’s how it works:

  1. The attacker identifies a part of the JavaScript code that dynamically manipulates the DOM based on user input.
  2. They inject a malicious payload into the input field, URL parameter, or fragment identifier (e.g., #value in a URL).
  3. When the vulnerable JavaScript executes, it processes the injected input directly, causing the malicious script to execute in the victim’s browser.

Key Characteristics:

  • Entirely Client-Side: Exploited within the browser, often bypassing server-side validation.
  • JavaScript-Driven: Relies on unsafe manipulation of DOM elements using JavaScript functions like innerHTML, eval(), document.write(), or document.location.
  • Harder to Detect: Traditional server-side monitoring tools cannot detect DOM-based XSS because no malicious payload reaches the server.

How DOM-based XSS Works

Vulnerable Code Example

Here’s a simple example of a vulnerable script:

const userInput = document.location.hash.substring(1);
document.getElementById('output').innerHTML = "Welcome, " + userInput;
Enter fullscreen mode Exit fullscreen mode

If a user visits the following URL:

http://example.com/#<script>alert('XSS')</script>
Enter fullscreen mode Exit fullscreen mode

The JavaScript will extract the fragment (<script>alert('XSS')</script>), concatenate it, and assign it to the innerHTML property of the output element. This leads to the execution of the attacker’s script.

Key JavaScript Functions Misused in DOM-based XSS:

  • innerHTML / outerHTML: Directly injects content into the DOM, interpreting HTML and scripts.
  • document.write(): Dynamically writes content into the DOM but also interprets JavaScript code.
  • eval(): Executes arbitrary code, making it a prime target for attackers.
  • document.location / window.location: Parses and uses URL fragments or query strings without proper sanitization.

Demo Attack: How DOM Manipulation Leads to Script Execution

Scenario: Search Results Page

Imagine a search page with the following script:

const query = new URLSearchParams(window.location.search).get('search');
document.getElementById('results').innerHTML = `Results for: ${query}`;
Enter fullscreen mode Exit fullscreen mode

Attack Steps:

  1. The attacker crafts a malicious URL:
   http://example.com/search?search=<script>alert('XSS')</script>
Enter fullscreen mode Exit fullscreen mode
  1. The victim clicks the link. The browser processes the search parameter and sets the content of the results element to:
   Results for: <script>alert('XSS')</script>
Enter fullscreen mode Exit fullscreen mode
  1. The browser executes the malicious script, displaying an alert box or, in a real attack, stealing sensitive data.

Prevention Methods for DOM-based XSS

DOM-based XSS can be mitigated through a combination of secure coding practices, JavaScript sanitization libraries, and leveraging modern frameworks and tools.

1. Avoid Dangerous JavaScript Functions

Avoid using insecure methods like innerHTML, document.write(), and eval() for handling user input. Instead:

  • Use textContent or setAttribute for text and attributes:
  const safeInput = document.createTextNode(userInput);
  document.getElementById('output').appendChild(safeInput);
Enter fullscreen mode Exit fullscreen mode
  • Replace innerHTML with safer alternatives, such as textContent:
  document.getElementById('output').textContent = "Welcome, " + userInput;
Enter fullscreen mode Exit fullscreen mode

2. Validate and Sanitize User Input

Always validate and sanitize user input on both the client and server side, even for data processed exclusively on the client.

For JavaScript:

  • Use libraries like DOMPurify to sanitize input before injecting it into the DOM:
  const sanitizedInput = DOMPurify.sanitize(userInput);
  document.getElementById('output').innerHTML = `Welcome, ${sanitizedInput}`;
Enter fullscreen mode Exit fullscreen mode

3. Content Security Policy (CSP)

A well-configured Content Security Policy (CSP) can block execution of unauthorized scripts, even if the attacker successfully injects one.

Example CSP:

Content-Security-Policy: default-src 'self'; script-src 'self';
Enter fullscreen mode Exit fullscreen mode

This policy only allows scripts from the same origin and blocks inline scripts.

4. Use Secure JavaScript Frameworks

Modern frameworks like React, Angular, and Vue automatically handle user input securely by escaping dangerous characters or separating HTML and script contexts. For example:

  • React automatically escapes dangerous characters like < and > in JSX, preventing script execution.

5. Avoid Trusting URL Fragments

Never use document.location.hash or other URL fragments directly in your application without sanitization. Consider parsing and validating query strings or fragments separately:

const params = new URLSearchParams(document.location.search);
const userInput = params.get('input');
const safeInput = DOMPurify.sanitize(userInput);
document.getElementById('output').textContent = safeInput;
Enter fullscreen mode Exit fullscreen mode

Best Practices for Handling Dynamic Data

  1. Escape Output Contextually:

    • Escape special characters based on where the data is used (HTML, JavaScript, CSS, or URLs).
  2. Avoid Inline JavaScript:

    • Minimize the use of inline JavaScript or event handlers (onclick, onload, etc.) to reduce the attack surface.
  3. Monitor and Test Regularly:

    • Perform regular security testing to identify DOM-based vulnerabilities. Tools like Burp Suite or OWASP ZAP can help detect these issues.
  4. Educate Developers:

    • Ensure developers understand the dangers of DOM-based XSS and the importance of secure coding practices.

Conclusion

DOM-based XSS represents a significant threat due to its entirely client-side nature and reliance on JavaScript manipulation. By understanding how attackers exploit vulnerabilities like innerHTML and document.location, developers can take proactive measures to secure their applications.

~Trixsec

Top comments (0)