Skip to content

Instantly share code, notes, and snippets.

@ddworken
Last active March 15, 2023 20:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ddworken/309363b5d140bcc5ff6b39fa4a8d7a12 to your computer and use it in GitHub Desktop.
Save ddworken/309363b5d140bcc5ff6b39fa4a8d7a12 to your computer and use it in GitHub Desktop.
Sandbox allow-unique-origin and customizing Blob response headers

Sandbox allow-unique-origin and customizing Blob response headers

A Web Platform API proposal to improve sandboxing and Blob URL

David Dworken, Feb 2023 (©2023, Google)

TL;DR: Add a allow-unique-origin sandbox directive and response headers to Blob URLs

allow-unique-origin sandbox directive

Add a new sandbox directive, allow-unique-origin, that causes the rendered content to execute in a unique non-null origin.

The origin of a document sandboxed in this way will be sandbox:[$RANDOM_UUID][$ORIGINAL_ORIGIN]. For example, if https://example.com set a CSP: sandbox allow-unique-origin header, then the origin of the document would be sandbox:[9138ee47-c4f7-4e30-8751-acf51834e3f6][https://example.com]. Having a unique origin that contains the original origin, makes it possible to implement a number of useful product features like checking the original origin when responding to CORS requests.

These sandboxed pages will have access to isolated new storage partitions with a lifetime scoped to the current page. This includes document.cookie, window.localStorage, window.caches, and more.

Blob URL Response Headers

Expose a headers option to BlobPropertyBag in the Blob constructor. The headers option will take in an object containing response headers to associate with the Blob.

This will enable a number of new uses for Blobs including creating Blob URLs that are guaranteed to not lead to XSS vulnerabilities:

const blob = new Blob([untrustedHTML], {
      type: 'text/html',
      headers: {
        'Content-Security-Policy': 'sandbox allow-scripts allow-unique-origin'
      },
    });
const safeBlobUrl = URL.createObjectURL(blob);
iframe.src = safeBlobUrl;
window.open(safeBlobUrl);

In this case, safeBlobUrl is guaranteed to render in a unique origin no matter how it is used, and thus it cannot lead to an XSS vulnerability in the creating application.

When a Blob sets a CSP policy that includes the sandbox directive, it does not inherit the CSP of the creator. This matches the web's model where loading an iframe with a distinct origin also does not inherit the CSP of the parent page.

Why do we need this?

It aims to solve three problems.

1. XSS through Blob URLs

Blob URL is useful for loading locally available resources. However it also leads to XSS bugs when Blob URLs are created with untrusted content.

  1. XSS on WhatsApp Web.
  2. XSS on Shopify.
  3. XSS on chat.mozilla.org.

This proposal makes it possible to create sandboxed Blob URLs that are guaranteed not to cause an XSS (because any JS will execute in a unique origin).

2. A web-platform alternative to sandbox domains

Many Web apps require a place to host user contents (e.g. usercontent.goog, dropboxusercontent.com, etc) to safely render them. In order to do so securely (e.g. to avoid exploitable XSS, cookie bombing, and Spectre attacks), many sites rely on sandbox domains to get unique origins. Sandbox domains entail significant complexity for authentication (especially if one also adds it to the public suffix list) and are very hard to do right. Adding the ability to create Blob URLs, iframes, and top level pages with unique origins can replace this complexity with a native web platform API.

3. Increasing the applicability of sandboxing

CSP sandbox is widely used for isolating content, but running in a null origin significantly limits many uses:

  • Sandboxed content can't access any storage APIs. Many third-party scripts (e.g. analytics scripts) rely on being able to access these APIs, so it becomes impossible to sandbox these pages.
  • Sandboxed content sends requests with a Origin: null header, so there is no way for services to make fine grained decisions based on exactly what content is executing in a given sandboxed page.

Supporting a unique origin, fixes both of these limitations and will enable more services to use sandboxing to isolate untrusted content.

FAQ

Compared to that proposal, this doc proposes a more generic solution for creating unique origins that can be used for more than just Blob URLs. This makes it easier to use this feature to refactor existing web applications.

However, there are a few downsides to this proposal:

  1. It does not fully address XSS issues stemming from Blob URLs. SVG use elements could point to a same-origin sandboxed Blob URL and thus lead to unsandboxed code execution.
    • From my perspective, this seems acceptable since this is a very rarely used feature.
  2. It does not provide a way to create a secure postMessage channel with an iframe that has a unique origin. This is because attackers can swap out child iframes on any page that doesn't enforce both COOP and XFO/frame-ancestors.
    • From my perspective, this is a significant downside but there are workarounds (namely using a secret key passed into the iframe to establish the channel). In addition, in the long term there is hope that it may be possible to further restrict cross-origin frame src changes.

The format of cross-origin Blob URLs look weird. Why did you choose that?

Having origins like sandbox:[$RANDOM_UUID][$ORIGINAL_ORIGIN] make it possible for endpoints receiving CORS requests to ascertain both the original origin, and which unique origin a request is coming from. I'm not attached to this format, but I believe including this information is useful.

Why is CSP not inherited from the creator to Sandboxed Blob URLs?

A creator of cross-origin Blob URLs is often a sensitive origin. And therefore it is likely to have a strict CSP to defend against XSS attacks. However, the creator likely is using a sandboxed Blob URL in order to safely run arbitrary HTML/JS (because it acts like a sandbox domain), which is not possible if CSP is automatically inherited.

Conceptually, this isn't a CSP bypass since it is treating sandboxed Blob URLs identically to cross-origin iframes.

Some websites use CSP for containment purposes. In order to ensure that this doesn't bypass those CSPs we'll add a 'allow-sandboxed-blob' CSP keyword. This means that a CSP like frame-src blob: won't allow loading sandboxed blobs unless it is changed to frame-src blob: 'allow-sandboxed-blob'.

Should we expose all response headers or just some?

As described above, this makes it possible to set arbitrary response headers on a Blob URL. This is a useful capability, but there are some concerns that it could lead to unexpected behavior (e.g. what if someone sets a Location header on a Blob). If we believe that this is a problem, we could change the API to instead be more constrained like:

const blob = new Blob([untrustedHTML], {
      type: 'text/html',
      contentSecurityPolicy: 'sandbox allow-scripts allow-unique-origin',
      contentDisposition: 'inline',
    });
const safeBlobUrl = URL.createObjectURL(blob);
...

This would still target the two known use cases of setting response headers on a Blob.

Acknowledgements

Thank you to the following folks who provided insightful feedback which shaped this proposal.

Damien Engels, Jun Kokatsu, terjanq, Artur Janc, and Mike West.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment