This content originally appeared on web.dev and was authored by Krzysztof Kotowicz
Why should you care?
DOM-based cross-site scripting (DOM XSS) is one of the most common web security vulnerabilities, and it's very easy to introduce it in your application. Trusted Types give you the tools to write, security review, and maintain applications free of DOM XSS vulnerabilities by making the dangerous web API functions secure by default. Trusted Types are supported in Chrome 83, and a polyfill is available for other browsers. See Browser compatibility for up-to-date cross-browser support information.
Key Term:
DOM-based cross-site scripting happens when data from a user controlled
source (like user name, or redirect URL taken from the URL fragment)
reaches a sink, which is a function like eval()
or a property setter like
.innerHTML
, that can execute arbitrary JavaScript code.
Background
For many years DOM XSS has been one of the most prevalent—and dangerous—web security vulnerabilities.
There are two distinct groups of cross-site scripting. Some XSS vulnerabilities are caused by the server-side code that insecurely creates the HTML code forming the website. Others have a root cause on the client, where the JavaScript code calls dangerous functions with user-controlled content.
To prevent server-side XSS, don't generate HTML by concatenating strings and use safe contextual-autoescaping templating libraries instead. Use a nonce-based Content Security Policy for additional mitigation against the bugs as they inevitably happen.
Now a browser can also help prevent the client-side (also known as DOM-based) XSSes with Trusted Types.
API introduction
Trusted Types work by locking down the following risky sink functions. You might already recognize some of them, as browsers vendors and web frameworks already steer you away from using these features for security reasons.
-
Script manipulation:
<script src>
and setting text content of<script>
elements. -
Generating HTML from a string:
innerHTML
,outerHTML
,insertAdjacentHTML
,<iframe> srcdoc
,document.write
,document.writeln
, andDOMParser.parseFromString
-
Executing plugin content:
<embed src>
,<object data>
and<object codebase>
-
Runtime JavaScript code compilation:
eval
,setTimeout
,setInterval
,new Function()
Trusted Types require you to process the data before passing it to the above sink functions. Just using a string will fail, as the browser doesn't know if the data is trustworthy:
To signify that the data was securely processed, create a special object - a Trusted Type.
Trusted Types heavily reduce the DOM XSS attack surface of your application. It simplifies security reviews, and allows you to enforce the type-based security checks done when compiling, linting, or bundling your code at runtime, in the browser.
How to use Trusted Types
Prepare for Content Security Policy violation reports
You can deploy a report collector (such as the open-source go-csp-collector), or use one of the commercial equivalents. You can also debug the violations in the browser:
window.addEventListener('securitypolicyviolation',
console.error.bind(console));
Add a report-only CSP header
Add the following HTTP Response header to documents that you want to migrate to Trusted Types.
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Now all the violations are reported to //my-csp-endpoint.example
,
but the website continues to work. The next section explains how //my-csp-endpoint.example
works.
Caution:
Trusted Types are only available in a secure context
like HTTPS and localhost
.
Identify Trusted Types violations
From now on, every time Trusted Types detect a violation, a report will be sent
to a configured report-uri
. For example, when your application
passes a string to innerHTML
, the browser sends the following report:
{
"csp-report": {
"document-uri": "https://my.url.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://my.url.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
This says that in https://my.url.example/script.js
on line 39 innerHTML
was called with
the string beginning with <img src=x
.
This information should help you narrow down which parts of code may be
introducing DOM XSS and need to change.
Most of the violations like this can also be detected by running a code linter or static code checkers on your codebase. This helps quickly identify a large chunk of violations.
That said, you should also analyze the CSP violations, as these trigger when the non-conforming code is executed.
Fix the violations
There are a couple of options for fixing a Trusted Type violation. You can remove the offending code, use a library, create a Trusted Type policy or, as a last resort, create a default policy.
Rewrite the offending code
Perhaps the non-conforming functionality is not needed anymore or can be rewritten in a modern way without using the error-prone functions?
Use a library
Some libraries already generate Trusted Types that you can pass to the sink functions. For example, you can use DOMPurify to sanitize an HTML snippet, removing XSS payloads.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify supports Trusted Types and will return sanitized
HTML wrapped in a TrustedHTML
object such that the browser does not generate
a violation.
Caution: If the sanitization logic in DOMPurify is buggy, your application might still have a DOM XSS vulnerability. Trusted Types force you to process a value somehow, but don't yet define what the exact processing rules are, and whether they are safe.
Create a Trusted Type policy
Sometimes it's not possible to remove the functionality, and there is no library to sanitize the value and create a Trusted Type for you. In those cases, create a Trusted Type object yourself.
For that, first create a policy. Policies are factories for Trusted Types that enforce certain security rules on their input:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
This code creates a policy called myEscapePolicy
that can produce TrustedHTML
objects via its createHTML()
function. The defined rules will
HTML-escape <
characters to prevent the creation of new HTML elements.
Use the policy like so:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
While the JavaScript function passed to trustedTypes.createPolicy()
as
createHTML()
returns a string, createPolicy()
returns a policy object that
wraps the return value in a correct type - in this case TrustedHTML
.
Use a default policy
Sometimes you can't change the offending code. For example, this is the case if you're loading a third-party library from a CDN. In that case, use a default policy:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
The policy with a name default
is used wherever a string is
used in a sink that only accepts Trusted Type.
Use the default policy sparingly, and prefer refactoring the application to use regular policies instead. Doing so encourages designs in which the security rules are close to the data that they process, where you have the most context to correctly sanitize the value.
Switch to enforcing Content Security Policy
When your application no longer produces violations, you can start enforcing Trusted Types:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Voila! Now, no matter how complex your web application is, the only thing that can introduce a DOM XSS vulnerability, is the code in one of your policies - and you can lock that down even more by limiting policy creation.
Further reading
This content originally appeared on web.dev and was authored by Krzysztof Kotowicz
Krzysztof Kotowicz | Sciencx (2020-03-25T00:00:00+00:00) Prevent DOM-based cross-site scripting vulnerabilities with Trusted Types. Retrieved from https://www.scien.cx/2020/03/25/prevent-dom-based-cross-site-scripting-vulnerabilities-with-trusted-types/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.