DOM-based Cross-Site Scripting (DOM-based XSS)

DOM-based cross-site scripting (DOM-based XSS) is a type of attack that exploits the Document Object Model (DOM).

How does DOM-based XSS work?

The DOM is an internal data structure that stores all the objects and properties of a web page. For example, each tag used in HTML code represents a DOM object.

In addition, the DOM of a web page contains information about properties such as the page URL and metadata. Developers can access these objects and properties using JavaScript and modify them dynamically.

The Document Object Model is what helps to create dynamic single-page applications. However, it is also what makes DOM-based cross-site scripting possible.

Unlike all other types of cross-site scripting, DOM-based XSS is exclusively a client-side vulnerability. This means that the payload never reaches the server. The entire attack takes place in the web browser.

DOM-based cross-site scripting is similar to reflected XSS in that no information is stored during the attack. DOM-based XSS is also carried out by tricking the target into clicking on a malicious URL.

Sources and sinks in DOM-based XSS

Every DOM-based XSS vulnerability has two elements: the source of the user input and the place where it is written, called the sink.

Common sources that attackers can manipulate are document.URL, document.documentURI, location.href, location.search, location.*, window.name, and document.referrer.

Popular sinks include document.write, (element).innerHTML, eval, setTimeout, setInterval, and execScript.

For JavaScript code to be vulnerable to DOM-based XSS, it must receive information from a source that the attacker can control and then pass it to the sink.

DOM-based XSS Example

In this example, the developer wants to display the username on the dashboard page (dashboard.html). It is passed to the application as a parameter in the URL:

<html>
(...)
Dashboard for
<script>
   var pos=document.URL.indexOf("context=")+8;
   document.write(decodeURIComponent(document.URL.substring(pos)));
</script>
(...)
</html>

The script looks for context= in the URL (document.URL.indexOf(“context=”)), takes all the text to the right of it (+8 means 8 characters to the right of the beginning of context=), and uses document.write to insert this text directly into the HTML for the browser to interpret.

If this URL is called:

http://www.example.com/dashboard.html?context=Thomas

The page will say:

Dashboard for Thomas

DOM-based XSS attack

Important: The information is provided solely to raise awareness of risks and does not encourage illegal actions.

The attacker creates the following URL:

http://www.example.com/dashboard.html?context=
%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%22%4c%45
%41%56%45%20%54%48%49%53%20%50%41%47%45%21%20%59%4f
%55%20%41%52%45%20%42%45%49%4e%47%20%48%41%43%4b%45
%44%21%22%29%3b%3c%2f%73%63%72%69%70%74%3e

The long string of hexadecimal codes in this payload is the URL-encoded form of the following content:

<script>alert("LEAVE THIS PAGE! YOU ARE BEING HACKED!");</script>

The attacker then sends the URL to the target, for example, in an email. When the target clicks on it, the browser opens the dashboard.html page and runs the malicious script. This rewrites the document content and inserts the following tag into the HTML code that the browser interprets:

Dashboard for <script>alert("LEAVE THIS PAGE! YOU ARE BEING HACKED!");</script>

As a result, the browser displays a pop-up window that prompts people to leave the page. The consequence of this is that the targeted users will stop visiting the web application, fearing for their security.

Fix

The developer rewrites the code using a secure sink. As a result, untrusted content from the source will always be interpreted as text, not code:

<html>
(...)
Dashboard for <span id="contentholder"></span>
<script>
   var pos=document.URL.indexOf("context=")+8;
   document.getElementById("contentholder").textContent = 
       document.URL.substring(pos,document.URL.length);
</script>
(...)
</html>

The developer creates a placeholder and writes the username not directly in the HTML but in the textContent property of the object (using a safe sink). This ensures that the browser will not interpret this content as code and will simply display it as text.

Consequences of DOM-based XSS

Here are some actions that malicious hackers can take, based on the simple example given earlier:

  • They can create a phishing campaign and send out millions of emails containing a malicious link with a payload that redirects users to a page designed to mimic a web application. As a result, millions of users can lose their credentials and blame the website, which can seriously damage their reputation.
  • Attackers can craft a payload that redirects the person to a page that mimics a login to the application. They can then send this malicious URL to internal users, even the CEO. If even one of them falls for this trick, the attacker will have their credentials to escalate the attack. Ultimately, this could allow them to gain access to other computer systems in the organization.

How to Prevent DOM-Based XSS

The best way to completely avoid DOM-based XSS vulnerabilities in JavaScript code is to use a proper output method (a safe sink).

It is worth noting that not all DOM elements have a safe output method. There are times when you simply want to avoid using untrusted data.

Teams can also apply typical XSS protection techniques (filtering and escaping) for JavaScript. Unfortunately, unlike server-side languages, there are no universal JavaScript libraries that can help filter and escape data, so developers have to write and maintain such functionality themselves.

How to detect DOM-based XSS

Such vulnerabilities can be detected using dynamic application security testing tools, such as DAST. For example, it can be Invicti (based on Acunetix and Netsparker). To test this solution for free, leave your contact details below and we will contact you:

Request for free Invicti Trial



    Subscribe to news