Is the “iframe sandbox” technique safe?

2020-05-23 02:55发布

问题:

Update: Since this is unanswered, I'm changing the question slightly. Comments on the post on Dean's blog linked below indicate this technique does not work in Safari.

My question now is: does the technique described below work* in modern browsers, and in particular can someone confirm whether it works in Safari?

Here's a more recent blog post. It says at one point:

Sandboxed natives ... are supported in a variety of browsers, including ... Safari 2.0+

...but later says that the iframe technique is "supported by all major browsers except Safari," and the fallback he shows involves doing some weird stuff with faked constructors and __proto__ that seems a bit hacky.

I almost find it hard to believe that two different windows could actually share the same, say, Object.prototype. What happens with cross-domain iframes? If I modify prototypes in one frame, do the prototypes in the other frame get modified? This seems like an obvious security concern. Someone please shed some light on this situation.

* By "work" I mean My.Object != Object, so the prototypes can be modified in one window without affecting the other.


Original post

I know this has been asked before, but I have a specific solution in mind, and I want to know if this type of solution has been discussed before and where I might learn how reliable and well-accepted it is.

The question is how to extend native types in javascript without actually messing with the types themselves, so just altering Array.prototype is no good (maybe other code is using for..in with arrays). Creating a fake constructor that returns a native array with some functions tacked on doesn't seem like a good solution either, actually extending the native objects seems better. But you can't do the normal javascript dummy function prototype switcharoo style extension with native types either, because you'll get errors like "push is not generic" when you try to call native functions.

So, the solution I have in mind works like this: create another window, add functionality to prototypes of native constructors in that window, and use those constructors in your program.

This example extends Array as My.Array with an each function and String as My.String with an alert function.

    var My = (function(){

      // create an iframe to get a separate global scope
      var iframe = document.createElement('iframe');
      iframe.style.height = '0px';
      iframe.style.width = '0px';
      iframe.style.border = 'none';
      iframe.style.position = 'absolute';
      iframe.style.left = '-99999px';
      document.documentElement.appendChild(iframe);
      var My = iframe.contentWindow;

      My.String.prototype.alert = function(){
        alert(this);
      }

      My.Array.prototype.each = function(callback){
        for (var i=0, l=this.length; i<l; i++) {
          callback(this[i], i);
        }
      }

      return My;

    }());

Again, my question is whether this approach has been discussed before, what it's called, where I can find more information, etc. I'd like to know if there is a cleaner way to get another global scope without using an iframe, or if it's possible this will fail for some reason in certain javascript engines, or if anyone thinks it's a particularly bad idea or whatever.


Update: I guess people are calling this kind of thing an iframe sandbox, not to be confused with the HTML5 iframe sandbox attribute.

related:

http://dean.edwards.name/weblog/2006/11/hooray/

http://webreflection.blogspot.com/2008/03/javascript-arrayobject.html

回答1:

I ran this page in various browsers (Safari, Opera, IE7-9, Chrome, Firefox) and got consistent results with everything but firefox, in firefox the prototypes are sandboxed so that's good but the second test fails for some reason in firefox. The iframe prototype isn't immediately augmented. But this doesn't matter if you didn't mean to augment it anyway. You could try running it in more browsers to test.

Note that this doesn't really test any of the quirks for example (My.Array().slice would return the main window arrays depending on browser...) and there could be more. So I would say it's pretty unsafe.

It's an overkill anyway and seems too much work for no real gain.

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script type="text/javascript">
(function(){
    var ifr = document.createElement("iframe"),
        callbacks = [],
        hasReadyState = "readyState" in ifr;

    ifr.style.display = "none";


    document.body.appendChild(ifr);
    ifrDoc = ifr.contentWindow.document;
    ifrDoc.open();
    ifrDoc.write( "<!DOCTYPE html><html><head></head><body>"+"<"+"script"+">var w = this;"+"</"+"script"+">"+"</body></html>");
    ifrDoc.close();

    if( hasReadyState ) {

        ifr.onreadystatechange = function(){
            if( this.readyState === "complete" ) {
                fireCallbacks();
            }
        };

    }

    function fireCallbacks(){
        var i, l = callbacks.length;
        window.My = ifr.contentWindow.w;

        for( i = 0; i < l; ++i ) {
            callbacks[i]();
        }

        callbacks.length = 0;


    }

    function checkReady(){

        if( hasReadyState && ifr.readyState === "complete" ) {
        fireCallbacks();
        }
        else if( !hasReadyState ) {
        fireCallbacks();
        }
    }

    window.MyReady = function(fn){
        if( typeof fn == "function" ) {
            callbacks.push( fn );
        }
    };


window.onload = checkReady; //Change this to DOMReady or whatever
})()


MyReady( function(){

    My.Object.prototype.test = "hi";

    var a = new My.Object(),
        b = new Object();

    console.log( Math.random(), My.Object !== Object && b.test !== "hi", a.test === "hi" );

});
</script>

</body>
</html>


回答2:

If you have two different frames with content loaded from different domains, then no modern browser will allow any interaction on the JavaScript level between them for obvious security reasons. Your best bet of course would be to setup a test and see what happens for yourself, but I'm pretty sure that what you're describing should be safe on most browsers.