Trusted Types API for JavaScript DOM Security

Protecting against DOM XSS security vulnerabilities in JavaScript

Viduni Wickramarachchi
Bits and Pieces

--

Have you heard of DOM XSS attacks? For those of you who haven’t,

DOM XSS attacks are where the hacker attack payloads are executed as a result of modifying the DOM environment in the browser.

This makes the client-side code run unexpectedly. Therefore, the vulnerability exists in the client-side code. Attackers make use of instances where they can inject harmful payload into your code to make these attacks possible. This is an injection attack.

DOM XSS injections can happen in various ways. Some of these are as follows.

  • Setters for Element attributes that accept a URL of the code to load like HTMLScriptElement.src
  • Setters for Element attributes that accept a code to execute like HTMLScriptElement.text
  • Functions that execute code directly like eval
  • Navigation to ‘javascript:’ URLs

Therefore we need to take the required measures to avoid these issues at any cost.

Trusted Types API to Rescue

The team at Google Chrome announced an API called Trusted Types API in order to control these DOM XSS security vulnerabilities. They mainly introduced this as DOM-based XSS vulnerabilities were growing as opposed to server-side XSS vulnerabilities.

This is because DOM XSS is easy to introduce, but harder to detect.

How does the trusted types API prevent DOM XSS attacks?

Trusted Types API enables addressing the XSS problems at the root cause itself. Let’s find out how.

The DOM API is not secure by default. Below are some examples,

eval('foo()');
document.createElement('div').innerHTML = '<foo>';
document.createElement('a').setAttribute('onclick'', 'foo()');

It is very easy to inject a malicious script or malicious HTML into these.

In order to avoid this, the Trusted Types API enables setting the Content Security Policy (CSP) HTTP response header to Content-Security-Policy: trusted-types * to leverage only Trusted Types. This will enable the developer to block dangerous injections so that they get secure by default.

This can be enabled as follows.

Content-Security-Policy: trusted-types;
Content-Security-Policy: trusted-types 'none';
Content-Security-Policy: trusted-types <policyName>;
Content-Security-Policy: trusted-types <policyName> <policyName> 'allow-duplicates';

The trusted-types directive instructs the browser to build non-spoofable, typed values to be passed to DOM XSS sinks in place of strings.

The main idea here is to pass objects to DOM sinks instead of strings. The DOM supports the passing of objects.

elem.innerHTML = { toString: function() { return 'Hello World' }};
elem.innerHTML // returns Hello World

What Trusted Types API recommends is to pass typed objects instead of plain JS objects.

This will enable DOM sinks to reject strings and only accept the matching type.

Using Trusted Types API in practice

If you are planning to implement the Trusted Types API in your project, at first, you need to find out where Trusted Types are violated. This can be done very easily.

Check for Trusted Types violations

Add the following HTTP response header to the files you need to check for violations.

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //mysite.com/cspViolations

Then, all the violations will be reported to //mysite.com/cspViolations . This will not hinder any functionality of your site.

Trusted types violations report

When a Trusted Type violation is detected, it will be sent to a report which was configured using report-uri. For example, if you have passed a string to innerHTML, something similar to the below report will be generated.

{
"csp-report": {
"document-uri": "https://mysite.com",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 20,
"column-number": 12,
"source-file": "https://mysite.com/dashboard.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}

This will help you identify which lines of code in which files will be causing DOM XSS vulnerabilities on your site.

Resolving the violations

There are few methods we can use to remove a Trusted Type violation. Let’s look at these methods one by one.

1. Rewrite the violating code

For example, if you a piece of code such as el.innerHTML = '<img src=xyz.jpg>';, this could be re-written as follows.

el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);

This avoids directly assigning a string to innerHTML .

2. Using a library

A library such as DOMPurify can be used to sanitize HTML which returns HTML wrapped in a TrustedHTML object. This disallows the browser to generate a violation.

3. Create a Trusted Type policy

Instead of using a library or removing vulnerable code, you can create a Trusted Type object yourself. Trusted Types policies enforce security rules on their inputs.

  • Step 1 — Create a policy
if (window.trustedTypes && trustedTypes.createPolicy) {
const escapeHTMLPolicy = trustedTypes.createPolicy('escapePolicy',
{
createHTML: string => string.replace(/\</g, '&lt;')
});
}

This rule will omit < characters to prevent creating new HTML elements. createPolicy() returns a policy object that wraps the return value in a correct type; in this case TrustedHTML .

  • Step 2 — Use the policy
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped; // '&lt;img src=x onerror=alert(1)>'

If you cannot change the code (e.g., When you are sourcing a third-party library from a CDN), you can use a default policy.

if (window.trustedTypes && trustedTypes.createPolicy) {
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}

This is a conjunction of a policy and a purifying library.

Once you have resolved all your violations, you can enforce Trusted Types in your code. This is done as follows.

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //mysite.com/cspViolations

You may notice that the -Report-Only suffix is not included in here compared to our previous implementation.

In this section, we only looked at resolving HTML violations. However, Trusted Types API has the capability to detect violations and enforce rules on the following.

  • HTML
  • Script
  • URL
  • ScriptURL

Refer to this document for more information about these various types.

Benefits of enforcing Trusted Types

You may have grasped the benefits of enforcing Trusted Types in your applications from the previous sections already. I’ve listed them down as a summary for your reference. These are the high-level benefits of using this API.

  • Reduces attack surface on your site — Application is secure
  • Security validations at compile time and run time
  • Backward compatibility — Ability to use Trusted Types in the place of strings
  • Complements other security solutions such as CSPs for server-side XSS

Before we conclude, let’s have a quick look at browser compatibility as well.

Browser Compatibility

Unfortunately, out of the most common browsers, Firefox and Internet Explorer do not have support for Trusted Types. However, if you are a hard-core Chrome user, you are in luck!

Reference Source

Conclusion

Nowadays DOM XSS attacks are very common and increasing drastically. Detecting these are much harder than we think. Therefore, in order to keep our application secure from these attacks, implementing the Trusted Types API is the best solution.

This can be done easily and quickly as we discussed before. Cheers to building secure applications.

Thanks for reading!

Autonomous teams building together

Building monolithic apps means everyone works together, in one codebase, and on the same bloated version. This often makes development painful and slow as you scale.

But what if instead, you build independent components first, and then compose apps? Autonomous teams, building together!

Every team could work in their own codebase, develop and deploy their own features, and continuously collaborate with others to share and use each other’s components.

OSS Tools like Bit offer a powerful developer experience for doing exactly that. Many teams start by building their Design Systems or Micro Frontends, through components. Give it a try →

An independently source-controlled and shared “card” component. On the right => its dependency graph, auto-generated by Bit.

--

--