DOM XSS

4 minute read

What is Document Object Model(DOM)?

When we type a URL the server sends back raw HTML. The browser turns that into something visual and interactive by parsing the HTML and creating a structured in memory representation of every element. This representation is the DOM.

When a browser interprets the HTML it renders the individual HTML elements. This rendering creates objects of each elements for display. For example: Consider this simple HTML:

<div id="container">
  <h1 class="title">Hello</h1>
  <p>Welcome to the site</p>
</div>

The browser reads this and creates objects for each element — a div object, an h1 object nested inside it, a p object as a sibling.

These objects are arranged in a parent child hierarchy, forming a tree structure. Each object carries its properties: the div knows its id is "container", the h1 knows its class is "title".

So the hierarchical tree created by the objects that represent the individual HTML elements make up the DOcument Object Model

Why DOM?

But modern web applications are interactive like: the dropdown menus, search filters, form validation, all of these require the page to change after it’s been loaded.

There are two approaches to making pages dynamic:

  • Server-side approach: Every time something needs to change, send a request to the server, the server generates new HTML, sends it back, and the browser re-renders the whole page. This is slow, every interaction requires a network round trip.
  • Client-side approach: Use JavaScript running in the browser to directly modify the DOM. The changes happen instantly, no server round trip needed.

Browsers generate a DOM from HTML so they can enable programmatic manipulation of a page via JavaScript. Developers may use JavaScript to manipulate the DOM for background tasks, UI changes, etc, all from the client’s browser.

How Javascript manipulates DOM?

JavaScript accesses the DOM through the Document interface. Document is like the entry point, it’s a global object in the browser that represents the entire page and provides methods to find and manipulate elements.

To query for an object on the DOM, the document interface implements APIs like getElementById, getElementsByClassName, and getElementsByTagName. The objects that are returned from the query inherit from the Element base class.

document.getElementById("container")         // returns the div with id="container"
document.getElementsByClassName("title")      // returns elements with class="title"
document.getElementsByTagName("p")            // returns all <p> elements

(A base class is an object oriented programming concept. It’s a parent class that defines common properties and methods that all its children share. It is like a blueprint that all DOM elements are built from.)

The Element class contains properties like innerHTML to manipulate the content within the HTML element. The Document interface allows for direct writing to the DOM via the write() method.

The Element base class gives every DOM element a shared set of properties and methods, including things like:

  • innerHTML : read or write the HTML content inside an element
  • outerHTML : read or write the element itself and its content
  • id : the element’s id attribute
  • className : the element’s class attribute

Every DOM element, regardless of its type (div, p, img, input, etc.), inherits from the Element base class.

Manipulating elements:

let heading = document.getElementById("container");
heading.innerHTML = "<h1>New content</h1>";

Or writing directly to the page:

document.write("<p>Injected paragraph</p>");

Where DOM based XSS comes in?

DOM-based XSS can occur if unsanitized user input is provided to a property, like innerHTML or a method like write(). That is when unsanitized user input flows from a JavaScript accessible source into a dangerous sink that interprets it executable code.

Consider the following HTML page:

<!DOCTYPE html>
<html>
<head>
  <script>
    const queryString = location.search;
    const urlParams = new URLSearchParams(queryString);
    const name = urlParams.get('name');
    document.write('<h1>Hello, ' + name + '!</h1>');
  </script>
</head>
</html>
  • Here location.search is the source. It extracts everything after the ? in the URL. For http://target/page.html?name=Dummy, this gives ?name=Dummy.
  • Using the URLSearchParams interface, the constructor will parse the query string and return a URLSearchParams object, which is saved in the urlParams variable.
  • Next, the name parameter is extracted from the URL parameters using the get method. Finally, an h1 element is written to the document using the name passed as a query string.
  • document.write(); is the sink. document.write() takes the concatenated string and writes it directly into the page.

When the URL is page.html?name=<script>alert(1)</script>, the browser executes:

document.write('<h1>Hello, <script>alert(1)</script>!</h1>');

The browser parses this as HTML, encounters the <script> tag, and executes it. The attacker’s JavaScript runs in the context of the page. In a real attack, this could steal cookies, hijack sessions, or redirect the user.

Sources and Sinks

A DOM-based XSS vulnerability exists when data flows from any source to any sink without proper sanitization.The core methodology for finding DOM based XSS is tracing data flow from sources to sinks.

Common Sources

Sources are where attacker controlled data enters JavaScript:

Source Description
location.search URL query string (?param=value)
location.hash URL fragment after #
location.href The full URL
document.referrer The referring page’s URL
document.cookie Cookie values
window.name The window’s name property (persists across navigations)

Common Sinks

Sinks are dangerous functions or properties that process the data:

Sink Risk
document.write() / writeln() Writes raw HTML to the page
element.innerHTML Parses and renders HTML inside an element
element.outerHTML Replaces the element itself with parsed HTML
element.insertAdjacentHTML() Inserts parsed HTML at a specified position
eval() Executes a string as JavaScript code
setTimeout() / setInterval() Executes string arguments as JavaScript
location.href / location.assign() Can cause open redirects
element.src Can load attacker controlled resources

Updated: