Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Hook URL object creation #45

Closed
wants to merge 7 commits into from
Closed

[WIP] Hook URL object creation #45

wants to merge 7 commits into from

Conversation

weizman
Copy link
Member

@weizman weizman commented Jan 1, 2023

This is a fix attempt to issue #43 introduced by @arxenix where Snow can be bypassed by leveraging an iframe with src to a blob: URI.

Fixing this was tricky so I'll walk through the PR:

  1. Hooking into the Blob/File APIs didn't seem effective to me because you can create attackable blobs/files in various ways, so it seemed to be more effective to wait for those to be turned into actual URLs by hooking into URL.createObjectURL.
  2. My thinking was to understand the contents of the blob/file and determine whether it needs to be hooked by Snow or not.
  3. The only way I could think of for telling the contents of the blob/file was to turn it into a url, fetch its content, and hook it with Snow only if the content is expected to run as html (the only content that needs to be hooked by Snow is of type text/html which can be used to load a new same origin malicious realm).
  4. Since Snow must act synchronously, I had to use a sync XHR call to fetch the contents of the blob/file.
  5. With that same request I also safely determine the mime type of the blob/file (doing so by observing the provided type via the hook is unsafe because the type is provided within the options argument which is an object which means an attacker can provide a polluted options object that can prevent Snow from telling the real type).
  6. If the type is different from text/html I simply return the new URL as it cannot bypass Snow.
  7. Otherwise, I edit the html contents to include the Snow hook at the beginning, before the html gets to run.
  8. Since URL.createObjectURL also accepts MediaSource objects which cannot be synchronously fetched with XHR and are not a threat to Snow, I make sure to mark all new Blobs and Files with a non-writable non-configurable property that safely indicates this is a blob/file so attackers won't be able to hide that fact.
  9. By relying on that hardened property Snow can make sure to only attempt to protect blobs and files and not anything else (Otherwise fetching for a MediaSource causes an exception to be thrown).
  • All was tested on both Chrome and Safari.
  • All was tested on double-digit amount of major and popular websites to test for backwards compatibility.

@weizman weizman mentioned this pull request Jan 1, 2023
@arxenix
Copy link

arxenix commented Jan 1, 2023

I haven't looked through the patch or done any testing yet (only read info above), but I'm pretty confident that only hooking text/html is insufficient. You can get same-origin script execution in svg and xml documents, for example (potentially others as well?)

@arxenix
Copy link

arxenix commented Jan 1, 2023

Another thing that would need to be considered -- you can create a Worker from a Blob (doesn't matter what the mimetype of the blob used for the worker is), and can also call URL.createObjectURL from the Worker (which I believe snow wouldn't hook).

workerJs = `
postMessage(URL.createObjectURL(new Blob(["<script>alert(document.domain)</script>"], {type: "text/html"})));
`
workerBlob = new Blob([workerJs], {type: "text/plain"})

w = new Worker(URL.createObjectURL(workerBlob))
w.onmessage = (msg) => {
    console.log(msg);
    f = document.createElement("iframe");
    document.body.appendChild(f)
    f.src = msg.data;
}

@weizman
Copy link
Member Author

weizman commented Jan 1, 2023

#45 (comment) "I haven't looked through the patch or done any testing yet (only read info above), but I'm pretty confident that only hooking text/html is insufficient. You can get same-origin script execution in svg and xml documents, for example (potentially others as well?)"

Yea I'm not 100% confident about text/html but figured either way we're talking about a closed group. I researched for other examples but none worked for me except text/html - mind demonstrating what you had in mind?

@weizman
Copy link
Member Author

weizman commented Jan 1, 2023

#45 (comment) "Another thing that would need to be considered -- you can create a Worker from a Blob (doesn't matter what the mimetype of the blob used for the worker is), and can also call URL.createObjectURL from the Worker (which I believe snow wouldn't hook)."

workerJs = `
postMessage(URL.createObjectURL(new Blob(["<script>alert(document.domain)</script>"], {type: "text/html"})));
`
workerBlob = new Blob([workerJs], {type: "text/plain"})

w = new Worker(URL.createObjectURL(workerBlob))
w.onmessage = (msg) => {
    console.log(msg);
    f = document.createElement("iframe");
    document.body.appendChild(f)
    f.src = msg.data;
}

Oh wow, that's very clever, I did not consider that...
Any ideas then? This shakes the ground under my attempt I think, am open to other suggestions.

@arxenix
Copy link

arxenix commented Jan 1, 2023

#45 (comment) "I haven't looked through the patch or done any testing yet (only read info above), but I'm pretty confident that only hooking text/html is insufficient. You can get same-origin script execution in svg and xml documents, for example (potentially others as well?)"

Yea I'm not 100% confident about text/html but figured either way we're talking about a closed group. I researched for other examples but none worked for me except text/html - mind demonstrating what you had in mind?

  1. For SVG, a script tag could simply be in the SVG, and the browser will execute it (syncFetch would report the type as image/svg+xml)
f = document.createElement('iframe');
document.body.appendChild(f);

svg = `<svg xmlns="http://www.w3.org/2000/svg">
   <script>
      alert(window.origin);
   </script>
</svg>`
bloburl = URL.createObjectURL(new Blob([svg], {type: "image/svg+xml"}))
f.src = bloburl
  1. For XML: XSLT could be used to transform the XML to HTML (syncFetch would still report the type as text/xml)
f = document.createElement('iframe');
document.body.appendChild(f);

xslt = `<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="/asdf">
    <HTML>
      <BODY>
      <script>alert(window.origin);</script>
      </BODY>
    </HTML>
  </xsl:template>
</xsl:stylesheet>`
xml = `<?xml version='1.0'?>
<?xml-stylesheet type="text/xsl" href="data:text/xml;base64,${btoa(xslt)}" ?>
<asdf>meep</asdf>`
bloburl = URL.createObjectURL(new Blob([xml], {type: "text/xml"}))
f.src = bloburl

@weizman weizman changed the title Hook URL object creation [WIP] Hook URL object creation Jan 3, 2023
@weizman
Copy link
Member Author

weizman commented Jan 3, 2023

Thanks for the demonstrations, this is definitely a problem and this solution is clearly far from being complete.
Will have to revisit this to see what's the best way to approach this.
Am always open to suggestions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants