Skip to main content

Trusted Types - a new way to protect web application code from XSS attacks

Google has developed an API that allows modern web applications to protect their frontend from XSS attacks, and more specifically from JavaScript DOM injection (DOM-Based Cross Site Scripting).

Cross-site scripting (XSS) is the most common type of attack related to vulnerabilities in modern web applications. This is recognized not only by Google, but by the entire industry. Experience shows that developing a web application resistant to XSS attacks is still not a trivial task, especially when it comes to complex projects. While on the backend developers are quite successful in solving this problem, on the frontend everything is much more complicated. As part of Google's Vulnerability Reward Program, more and more awards are received by developers who have proposed a solution to protect against DOM XSS attacks.

How did we get to this life, you ask? Probably ask. The fact is that modern web applications have two serious and unrelated problems ...

XSS is easy to inject into code

DOM XSS is a type of attack in which unfiltered data coming from the user or other browser APIs, along with injected js injections, enter the DOM. For example, consider a snippet for loading a stylesheet for some UI template that an application uses:

const templateId = location.hash.match(/tplid=([^;&]*)/)[1]; // ... document.head.innerHTML += `<link rel="stylesheet" href="./templates/${templateId}/style.css">`

In this case, a script injected by an attacker into location.hash (in this case, it plays the role of a data source, that is, source), gets into innerHTML (plays the role of a data receiver, that is, sink). Using this vulnerability, an attacker can spoof the URL that the victim will go to:

https://example.com#tplid="><img src=x onerror=alert(1)>

This injection can be easily overlooked, especially if the code changes frequently. For example, once upon a time templateId could be generated and checked on the server and after that it never occurred to anyone to check it again. When we write something in innerHTML, we only know for sure that its value is a string. But should the content of this line be trusted? Where did it actually come from? Moreover, the problem is not limited to innerHTML only. There are over 60 methods and properties in a typical DOM that have the same problem. So the DOM API is unsafe by default and requires a special approach to prevent XSS.

XSS is hard to track

The above code is just a visual example, so it's easy to see the error there. In practice, the source and sink values ​​are often found in completely different parts of the application. Are there any functions that sanitize and validate data on the path from source to sink? How do you know when to call which function?

Looking only at the source code, it is difficult to know if it contains DOM XSS scripts. Moreover, it is not enough to grep the .js files with case sensitivity setting. After all, case-sensitive functions are often used inside various wrappers, so real vulnerabilities are well disguised and may look like this.

And sometimes, even if you read the entire code carefully, it is impossible to determine if there is a vulnerability in the codebase. Take a look at this expression:

obj[prop] = templateID;

If obj points to a location object (above was location.hash) and the prop is 'href', this is most likely DOM XSS. However, you can only find out for sure while executing the code. Since any part of your application can hypothetically write something to the DOM, all code must pass manual security checks, and the reviewer must be extremely careful to spot an error. But one cannot completely rely on such a verification method, since no one has canceled the human factor.

Trusted Types

Trusted Types is a new browser API that will help you fundamentally address the issues listed above, including protecting web applications from DOM XSS.

API Trusted Types is built on a set of classes (policies) that block malicious injections. With it, the DOM ceases to be vulnerable: its elements receive values not through a string, but through a special object. In order to use Trusted Types, you need to initialize a special field in the Content Security Policy (CSP) HTTP response header:

Content-Security-Policy: trusted-types *

CSP is an additional layer of security that allows you to recognize and remediate certain types of attacks, such as Cross Site Scripting (XSS) and data injection attacks. To enable the CSP, the server must be configured to use the Content-Security-Policy HTTP header in its responses. And then no one will be able to write an arbitrary string to your DOM element:

const templateId = location.hash.match(/tplid=([^;&]*)/)[1]; // typeof templateId == "string" document.head.innerHTML += templateId // TypeError

Now you have to create and use special typed objects for this. These objects can only be created in your application using specially designed features called Trusted Type Policies. For instance:

const templatePolicy = TrustedTypes.createPolicy('template', { createHTML: (templateId) => { const tpl = templateId; if (/^[0-9a-z-]$/.test(tpl)) { return `<link rel="stylesheet" href="./templates/${tpl}/style.css">`; } throw new TypeError(); } }); const html = templatePolicy.createHTML(location.hash.match(/tplid=([^;&]*)/)[1]); document.head.innerHTML += html;

Here we create a policy for the template template that validates the passed ID parameter and generates the resulting HTML. The set of create * methods calls the appropriate user-defined function and wraps the result in a Trusted Type object. For example, templatePolicy.createHTML calls the validation function for templateId and returns a TrustedHTML object containing (see the very first example). At the same time, the browser allows writing to innerHTML (which, in theory, expects normal HTML), data of an object of type TrustedHTML. In addition to the TrustedHTML class, you can use the TrustedScript, TrustedURL, and TrustedScriptURL classes. It might seem that the only benefit of the new API is the ability to add this check:

if (/^[0-9a-z-]$/.test(tpl)) { /* allow the tplId */ }

Indeed, this line is necessary for XSS protection. But in fact, everything is much more interesting. With Trusted Types, solving DOM vulnerabilities comes down to smart policy enforcement. No other code does data validation as it passes from source to sink. Thus, only incorrect handling of policies can lead to security problems. In our example, it doesn't matter where the templateId value comes from, since the policy always checks first of all if it passed validation. This means that in the area for which a certain policy is responsible, any XSS attack will be repulsed:

Uncaught TypeError: Failed to set the ’innerHTML’ property on ’Element’: This document requires `TrustedHTML` assignment.

Limiting policies

Did you notice the value * we used in the Content-Security-Policy header? It indicates that the application can create an arbitrary number of policies, provided that each of them has a unique name. If an application can freely create too many policies, it can be confusing and harder to implement DOM XSS protection.

Therefore, we can limit this number by explicitly specifying the list of policy names that we will use. For instance:

Content-Security-Policy: trusted-types template

This will ensure that only one policy named template is created. Then we can easily identify it in the source code and test it to work. Thanks to this, we can be sure that the application will be protected from DOM XSS. And you will be happy!

In practice, modern web applications need few policies. A general recommendation is to create policies wherever the client code generates HTML or URLs — in script loaders, HTML template libraries, or in an HTML sanitizer. All the numerous dependencies that are not DOM related do not need to use policies. And then the correct use of Trusted Types is guaranteed to protect us from XSS.

Fast start

Below is an ultimate short introduction to the API. Over time, more and more code examples, tutorials, and documentation have emerged on how to write and rewrite applications using Trusted Types. We believe that the web development community has long been ready to start experimenting with this.

To use the new API on your site, it must be activated directly in your browser. If you just want to run it locally starting in Chrome 73, you can enable the feature on the command line:

chrome --enable-blink-features=TrustedDOMTypes

or

chrome --enable-experimental-web-platform-features

If you are using Google Chrome, you can enter chrome: // flags / # enable-experimental-web-platform-features in the address bar. All these options for launching the API make it active during one session.

If you experience crashes, use --enable-features \ u003d BlinkHeapUnifiedGarbageCollection as a workaround. See the discussion of bug 929601 for details.

There is also a polyfill that will allow you to use Trusted Types in many other browsers.

Popular posts from this blog

The Slowest Programming Languages

Today, there are about 700 well-known programming languages ​ ​ in the world, while only 250 are used. If a task could be measured in days earlier, it is now a fraction of a second. Here are some of the slowest dynamic typing programming languages. In electronics, speed and performance depend on the number of transistors that a particular chip can have. According to Moore's law, their number doubles every two years. This is why a modern hand-sized mobile phone is much faster than a room-sized supercomputer of the 90s. When it comes to computer science, the difference between faster code and a faster PC is quite bike. Intel and AMD create powerful computing systems in terms of speed. The task of programmers is to write reliable and fast programs. One of the things to consider when creating software is programming languages . Each of them is implemented in different ways, therefore it has its own merits. And speed may simply not be among them. If you need to create a program for comp...

How to succeed in IT if you are not a programmer

There is a statement that only mathematicians are taken in IT. Is that so? Today's world of information technology answers: no. Successful in this area is not the one who quickly thinks in his mind, but rather the one who understands human psychology, knows languages and is quite ambitious. What is the secret to success and how to develop in IT if you are not a programmer? Develop communication skills and emotional intelligence Language, successful communication, the ability to "see and hear" the interlocutor are basic factors for those who want to succeed in IT. Linguistic inclinations are known to be more important for learning programming languages ​ ​ and understanding code than mathematics. That is, languages ​ ​ are a factor that allows ordinary people to develop in IT. The ability to find a common language and establish the necessary connections, easily communicate, the ability to feel and understand a colleague, subordinate, partner and client is something that is...