Is it possible to restrict the scope of a javascri

2020-02-10 01:05发布

Suppose I have a variables in the global scope.

Suppose I wish to define a function which I can guarantee will not have access to this variable, is there a way to wrap the function, or call the function, that will ensure this?

In fact, I need any prescribed function to have well defined access to variables, and that access to be defined prior to, and separate from that function definition.

Motivation: I'm considering the possibility of user submitted functions. I should be able to trust that the function is some variety of "safe" and therefore be happy publishing them on my own site.

11条回答
唯我独甜
2楼-- · 2020-02-10 01:54

I'm going give a technical answer to your question with at least one possibility. Use the name of the global as an argument to that function:

someGlobal = 5;

function cantSeeThatGlobal(someGlobal) {
  console.log(someGlobal);
}

cantSeeThatGlobal();   // prints undefined
cantSeeThatGlobal(10); // prints 10

It would be better of course just to not use global variables ever.

查看更多
Explosion°爆炸
3楼-- · 2020-02-10 01:59

if you are talking about a function that is exposed to you by loading a third party script, you are pretty much out of luck. that's because the scope for the function is defined in the source file it's defined in. sure, you can bind it to something else, but in most cases, that's going to make the function useless if it needs to call other functions or touch any data inside it's own source file - changing it's scope is only really feasible if you can predict what it needs to be able to access, and have access to that yourself - in the case of a third party script that touches data defined inside a closure, object or function that's not in your scope, you can't emulate what might need.

if you have access to the source file then it's pretty simple - look at the source file, see if it attempts to access the variable, and edit the code so it can't.

but assuming you have the function loaded, and it doesn't need to interact with anything other than "window", and for academic reasons you want to do this, here is one way - make a sandbox for it to play in. here's a simple shim wrapper that excludes certain items by name

function suspectCode() {
    console.log (window.document.querySelector("#myspan").innerText);
    console.log('verbotten_data:',typeof verbotten_data==='string'?verbotten_data:'<BANNED>');
    console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>');    // undefined === we can't
    console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>'); 
    
    secret_data = 'i changed the secret data !';
    console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>');    // undefined === we can't
    console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>'); 
}

var verbotten_data = 'a special secret';

window.secret_data = 'special secret.data';


console.log("first call the function directly");
suspectCode() ;

console.log("now run it in a sandbox, which banns 'verbotten_data' and 'secret_data'");
runFunctionInSandbox (suspectCode,[
    'verbotten_data','secret_data', 
    
    // we can't touch items tied to stack overflows' domain anyway so don't clone it
    'sessionStorage','localStorage','caches',
    
    // we don't want the suspect code to beable to run any other suspect code using this method.
    'runFunctionInSandbox','runSanitizedFunctionInSandbox','executeSandboxedScript','shim',
    
    ]);

function shim(obj,forbidden) {
   const valid=Object.keys(obj).filter(function(key){return forbidden.indexOf(key)<0;});
   var shimmed = {};
   valid.forEach(function(key){
       try {
         shimmed[key]=obj[key];
       } catch(e){
           console.log("skipping:",key);
       }
   });
   return shimmed;
}

function fnSrc (fn){
  const src = fn.toString();
  return src.substring(src.indexOf('{')+1,src.lastIndexOf('}')-1);
}

function fnArgs (fn){
  let src = fn.toString();
  src = src.substr(src.indexOf('('));
  src = src.substr(0,src.indexOf(')')-1);
  src = src.substr(1,src.length-2);
  return src.split(',');
}


function runSanitizedFunctionInSandbox(fn,forbidden) {
    const playground = shim(window,forbidden);
    playground.window = playground;
    let sandboxed_code = fn.bind(playground,playground.window);
    sandboxed_code();
}

function runFunctionInSandbox(fn,forbidden) {
   
   const  src  = fnSrc(fn);
   const  args = fnArgs(fn);
   executeSandboxedScript(src,args,forbidden);
}

function executeSandboxedScript(sourceCode,arg_names,forbidden) {
   var script = document.createElement("script");
   script.onload = script.onerror = function(){ this.remove(); };
   script.src = "data:text/plain;base64," + btoa(
       [
            'runSanitizedFunctionInSandbox(function(',
            arg_names,
            ['window'].concat(forbidden),
            '){ ',
            sourceCode,
            '},'+JSON.stringify(forbidden)+')'
       ].join('\n')
    );
   document.body.appendChild(script);
}
<span id="myspan">Page Access IS OK<span>

or a slightly more involved version that allows arguments to be passed to the function

 
function suspectCode(argument1,argument2) {
    console.log (window.document.querySelector("#myspan").innerText);
    console.log(argument1,argument2);
    console.log('verbotten_data:',typeof verbotten_data==='string'?verbotten_data:'<BANNED>');
    console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>');    // undefined === we can't
    console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>'); 
    
    secret_data = 'i changed the secret data !';
    console.log('secret_data:',typeof secret_data==='string'?secret_data:'<BANNED>');    // undefined === we can't
    console.log('window.secret_data:',typeof window.secret_data==='string'?window.secret_data:'<BANNED>'); 
    
    
}

var verbotten_data = 'a special secret';

window.secret_data = 'special secret.data';


console.log("first call the function directly");
suspectCode('hello','world') ;

console.log("now run it in a sandbox, which banns 'verbotten_data' and 'secret_data'");
runFunctionInSandbox (suspectCode,['hello','sandboxed-world'],
    [
    'verbotten_data','secret_data', 
    
    // we can't touch items tied to stack overflows' domain anyway so don't clone it
    'sessionStorage','localStorage','caches',
    
    // we don't want the suspect code to beable to run any other suspect code using this method.
    'runFunctionInSandbox','runSanitizedFunctionInSandbox','executeSandboxedScript','shim',
    
    ]);

function shim(obj,forbidden) {
   const valid=Object.keys(obj).filter(function(key){return forbidden.indexOf(key)<0;});
   var shimmed = {};
   valid.forEach(function(key){
       try {
         shimmed[key]=obj[key];
       } catch(e){
           console.log("skipping:",key);
       }
   });
   return shimmed;
}

function fnSrc (fn){
  const src = fn.toString();
  return src.substring(src.indexOf('{')+1,src.lastIndexOf('}')-1);
}

function fnArgs (fn){
  let src = fn.toString();
  src = src.substr(src.indexOf('('));
  src = src.substr(0,src.indexOf(')'));
  src = src.substr(1,src.length);
  return src.split(',');
}


function runSanitizedFunctionInSandbox(fn,args,forbidden) {
    const playground = shim(window,forbidden);
    playground.window = playground;
    let sandboxed_code = fn.bind(playground,playground.window);
    sandboxed_code.apply(this,new Array(forbidden.length).concat(args));
}

function runFunctionInSandbox(fn,args,forbidden) {
   const  src  = fnSrc(fn);
   const  arg_names = fnArgs(fn);
   executeSandboxedScript(src,args,arg_names,forbidden);
}

function executeSandboxedScript(sourceCode,args,arg_names,forbidden) {
   var script = document.createElement("script");
   script.onload = script.onerror = function(){ this.remove(); };
   let id = "exec"+Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString();
   window.execArgs=window.execArgs||{};
   window.execArgs[id]=args;
   let script_src = [
                            'runSanitizedFunctionInSandbox(function(',
                            ['window'].concat(forbidden),
                            (arg_names.length===0?'':','+arg_names.join(","))+'){',
                            sourceCode,
                            '},',
                            'window.execArgs["'+id+'"],',
                             JSON.stringify(forbidden)+');',
                            'delete window.execArgs["'+id+'"];'
                       ].join('\n');
                       
   let script_b64 = btoa(script_src);
   script.src = "data:text/plain;base64," +script_b64;
   document.body.appendChild(script);
}
<span id="myspan">hello computer...</span>

查看更多
疯言疯语
4楼-- · 2020-02-10 02:00

A little late, but maybe it will help you a bit

function RestrictFunction(params) {

    params = ( params == undefined ? {} : params );
    var scope = ( params.scope == undefined ? window : params.scope );
    var data = ( params.data == undefined ? {} : params.data );
    var script = ( params.script == undefined ? '' : params.script );
    if (typeof params.script == 'function') {
        script = params.script.toString();
        script = script.substring(script.indexOf("{") + 1, script.lastIndexOf("}"));
        }

    // example: override native functions that on the white list

    var setTimeout = function(_function,_interval) {

        // this is important to prevent the user using `this` in the function and access the DOM
        var interval = scope.setTimeout( function() { 
            RestrictFunction({
                scope:scope,
                data:data,
                script:_function
                });
            } , _interval );

        // Auto clear long user intervals
        scope.setTimeout( function() {
            scope.clearTimeout(interval);
            } , 60*1000 );

        return interval;
        }       

    // example: create custom functions

    var trace = function(str) {
        scope.console.log(str);
        }   

    return (function() {

        // remove functions, objects and variables from scope

        var queue = [];
        var WhiteList = [
            "Blob","Boolean","Date","String","Number","Object","Array","Text","Function",
            "unescape","escape","encodeURI","encodeURIComponent","parseFloat","parseInt",
            "isNaN","isFinite","undefined","NaN",
            "JSON","Math","RegExp",
            "clearTimeout","setTimeout"
            ];

        var properties = Object.getOwnPropertyNames(scope);
        for (var k = 0; k<properties.length; k++ ) {
            if (WhiteList.indexOf(properties[k])!=-1) continue;
            queue.push("var "+properties[k]+" = undefined;");
            }   

        for (var k in scope) {
            if (WhiteList.indexOf(k)!=-1) continue;
            queue.push("var "+k+" = undefined;");
            }

        queue.push("var WhiteList = undefined;");   
        queue.push("var params = undefined;")   ;
        queue.push("var scope = undefined;")    ;
        queue.push("var data = undefined;") ;
        queue.push("var k = undefined;");   
        queue.push("var properties = undefined;");  
        queue.push("var queue = undefined;");   
        queue.push("var script = undefined;");  
        queue.push(script); 

        try {
        return eval( '(function(){'+ queue.join("\n") +'}).apply(data);' ); 
        } catch(err) { }

        }).apply(data);

    }   

Example of use

// dummy to test if we can access the DOM
var dummy = function() {

    this.notify = function(msg) {
        console.log( msg );
        };

    }

var result = RestrictFunction({

    // Custom data to pass to the user script , Accessible via `this`
    data:{
        prop1: 'hello world',
        prop2: ["hello","world"],
        prop3: new dummy()
        },

    // User custom script as string or function
    script:function() {

        trace( this );

        this.msg = "hello world";
        this.prop3.notify(this.msg);

        setTimeout( function() {
            trace(this); 
            } , 10 );

        trace( data );
        trace( params );
        trace( scope );
        trace( window );
        trace( XMLHttpRequest );
        trace( eval );

        return "done!"; // not required to return value...

        },

    }); 

console.log( "result:" , result );
查看更多
走好不送
5楼-- · 2020-02-10 02:04

You can't restrict the scope of a Function using the "call" or "apply" methods, but you can use a simple trick using "eval" and scoping to essentially hide any specific global variables from the function to be called.

The reason for this is because the function has access to the "global" variables that are declared at the scope that the function itself what declared. So, by copying the code for the method and injecting it in eval, you can essentially change the global scope of the function you are looking to call. The end result is essentially being able to somewhat sandbox a piece of javascript code.

Here's a full code example:

<html>
<head>
<title>This is the page title.</title>
<script>
    function displayTitle()
    {
        alert(document.title);
    }

    function callMethod(method)
    {
        var code = "" +
            // replace global "window" in the scope of the eval
            "var window = {};" +
            // replace global "document" in the scope of the eval
            "var document = {}; " +
            "(" +

            // inject the Function you want to call into the eval
                method.toString() +

            // call the injected method
            ")();" +
            "";
        eval(code);
    }

    callMethod(displayTitle);
</script>
</head>
<body></body>
</html>

The code that gets eval'd looks like this:

var window = {};
var document = {};
(function displayTitle()
{
    alert(document.title);
})();
查看更多
劫难
6楼-- · 2020-02-10 02:05

Create a local variable with the same name. If you have a global variable like this:

var globalvar;

In your function:

function noGlobal(); {
    var globalvar;
}

If the function refers to globalvar, it will refers to the local one.

查看更多
登录 后发表回答