LavaMoat / snow

Use Snow to finally secure your web app's same origin realms!
https://lavamoat.github.io/snow/demo/
MIT License
102 stars 9 forks source link

Snow can be bypassed with ...data: URI #73

Closed magicmac closed 1 year ago

magicmac commented 1 year ago

Hey Gal! Nice to "meet you". I was reading your DevTools detection mechanism yesterday and I ended up landing into this LavaMoat. The challenge looked interesting (you know, when it bites you, you can't stop!) so I give it a few tries, and luckily, it worked!

Here's the code. I believe the best strategy to patch this would be to check whenever a non-accessible domain has iFrames inside, and if that's the case you can continue iterating and testing until you make sure none has access.

Have a great day!

iFrame = document.createElement('iframe');
iFrame.src="data:text/html,A<iframe src=https://lavamoat.github.io/snow/></iframe>";
document.body.appendChild(iFrame);
// At this point, your fantastic script can't access the first iFrame because of the data: URI, however you should have access to the internal one as soon as it renders.

// Let's give a bit  of time for the internal iFrame to render before accessing its window object
setTimeout(() => {
    iFrame.contentWindow[0].alert.call( top, 'did it work?!' );
}, 500);
weizman commented 1 year ago

Hi @magicmac! BIG FAN HERE!

I Read many of your stuff over the years at https://www.brokenbrowser.com/ - I'm honored you dedicated time to breaking Snow!

This vulnerability is super interesting actually, because it demonstrates an inherent problem with Snow as a solution. This proves how 2 realms (one cross origin and one same origin) can help bypassing Snow in so many ways, so easily (🥲).

  1. When in top, create a cross origin realm that you control its contents.
  2. Instruct the cross origin realm to form a same (as top) origin realm. 2.1. Snow won't be able to hook into the cross origin realm, and therefore won't be able to protect the new same origin realm when is created.
  3. After the above has completed, again in top, access to new same origin realm via frames array.

This goes not only for data: iframes, but for any cross origin iframes an attacker can control. In other words, the attacker can also redirect their realm to //malicious.com which will load <script>document.write('<iframe src="${top.location.href}"></iframe>')</script>, and then once again access it from top and abuse it.

So the problem here is more around how do we protect against cross origin realms forming inside them same origin realms to be later abused?

My intuition is to enhance Snow's capabilities from just same origin realms, to also local cross origin realms. Because even though data: counts as cross origin, if it loads fully locally, Snow can hook into that as well.

So by protecting that vector, and also integrating frame-src CSP directive to defend against remote cross origin realms, Snow might still be able to complete its mission after all.

This will require:

  1. Adjusting Snow to hook into local cross origin realms (hopefully just data: but maybe more?).
  2. Making sure its clear to users that Snow is still vulnerable if not integrated alongside CSP.

This requires some thought, feedback is highly appreciated!

Thanks again @magicmac! What are your thoughts on this?

magicmac commented 1 year ago

Yeah, I agree with all of what you are saying, Gal. I thought about the intermediary malicious.com website but data: looked more elegant and didn't need an external resource :)

  1. I don't know how to hook into local cross origin iFrame when there's a cross-origin-parent before. The only quick thing that comes to my mind is an onreadystatechange / onload event of the first x-domain iFrame but (I've just tested) when the inner (same top domain) iFrame loads, it does not fire anything.
  2. Isn't there an easy way (getters? Proxy? A kind of override?) to intercept any access to "top"? If we can detect when anyone is accessing top.*, then something can be done. Otherwise, I'd think of a CSP as you are saying.

Anyway, Gal, fantastic work! I will keep coming from time to time to play with it. =)

weizman commented 1 year ago
  1. The only local cross origin that comes to mind that we should worry about is data:, so if I could somehow hook into creation of such realms, I can edit the URL to include a Snow protection (e.g. data:text/html,<script>alert.call(top)</script> -> data:text/html,<script>HOOK_SNOW_FIRST()</script><script>alert.call(top)</script>). That way this realm will also be protected by Snow and therefore its potential children as well.
  2. I wouldn't go down that road... There are just so many ways to access realms and the different APIs they offer, patching all of them will be far harder than patching realms creation. And most of realms access APIs aren't even overridable (window['top'] / window[0] / etc are configurable:false by default)
serapath commented 1 year ago

Just tinkered with https://lavamoat.github.io/snow/demo/#self-xss-challenge-msg and figured a straight forward datauri seems to be enough. It doesn't even require an additional iframe.

const content = `alert(123)`
const prefix = `data:text/html;charset=utf-8,`
const iframe = document.createElement('iframe')
iframe.src = `${prefix}<script>${content}</script>`
document.body.append(iframe)

I wonder, also for blob urls, would it be possible to either rewrite the url as you mention here https://github.com/LavaMoat/snow/issues/73#issuecomment-1465685301 or in the case of a blob url maybe you could just fetch the content of that blob url when the iframe enters the document and replace the blob url with a new blob url which uses the same content but adds the snow shim?

weizman commented 1 year ago

Yes, but data: is a cross origin to the top main realm we protect, and therefore is by definition out of Snow's scope 😊 (see old https://github.com/LavaMoat/snow/issues/7#issuecomment-1216339426 by @benjamingr).

As for the 2nd part, blobs have made life so complicated for Snow. Luckily, we landed some powerful protections for blobs, but their so messy that I wouldn't be surprised if are not bulletproof. Furthermore, protecting blobs required some rare scenarios in Snow where the protecting patch we apply not only applies Snow protection, but also alters/blocks natural browser behaviour (not proud of it, but must be done for some edge cases).

It's complicated with blobs because being a local resource, loading an HTML blob inside an iframe will execute the HTML before calling the iframe's event listener, which leaves Snow 2nd to run. This is frustrating because you can't reproduce this behaviour with iframes otherwise (except for about:blank, but about:blank doesn't load any arbitrary HTML to begin with).

There are so many PRs and issues about blobs in Snow that I can't really share all of them without making your head spin.

43 by @arxenix might be a good place to start

benjamingr commented 1 year ago

It's complicated with blobs because being a local resource, loading an HTML blob inside an iframe will execute the HTML before calling the iframe's event listener

Yeah, you would presumably want to patch the .src setter on iframes to intercept it before and if it's a data url (base64 encoded or otherwise) add snow to that realm.

Though as you mention you said it's out of scope.

weizman commented 1 year ago

Patching .src was declined after considering it seriously. The conclusion is that defending attributes modification in really hard, and most likely doesn't worth the effort and should be approached differently if possible.

We will try to inject ourselves into data:b64 frames because of the vector presented by @magicmac, we'll see where that takes us.

weizman commented 1 year ago

124 indeed helps, but #73 only closes when we address #122 too

weizman commented 1 year ago

128 fixes some problems from #124, bringing us closer to a solution for this problem.

Next step would be to address #122, so that it's clear for the users what they need to do to protect themselves fully against #73

weizman commented 1 year ago

This issue has (hopefully) come to an end thanks to #122 🎉

This is a very tricky exploit that Snow might not have much it can do about. Therefore it was decided that it would be best if Snow integration will include calling Snow in every HTML file served by the same origin.

Intuitively you might think "if so, no need for Snow then" - Well, you'd be surprised, there are many other types of same origin attacks regardless of this.

Thank you for this wonderful discovery @magicmac ❤️