Подтвердить что ты не робот

Можно ли ограничить область действия функции javascript?

Предположим, что у меня есть переменные в глобальной области.

Предположим, что я хочу определить функцию, которую я могу гарантировать, не будет иметь доступа к этой переменной, есть ли способ обернуть функцию или вызвать функцию, которая обеспечит это?

На самом деле мне нужна какая-либо предписанная функция, чтобы иметь четко определенный доступ к переменным, и этот доступ должен быть определен до и отдельно от определения этой функции.

Мотивация: Я рассматриваю возможность предоставления пользователем функций. Я должен быть в состоянии доверять, что функция представляет собой целый ряд "безопасных" и, следовательно, будет счастливой публикацией их на моем собственном сайте.

4b9b3361

Ответ 1

Запустите код в iframe, размещенном на другом Origin. Это единственный способ гарантировать, что ненадежный код изолирован и не доступен для доступа к глобальным или DOM страницы.

Ответ 2

Использование встроенных веб-рабочих позволяет запускать безопасные функции. Что-то вроде этого позволяет пользователю вводить javascript, запускать его и получать результат без доступа к вашему глобальному контексту.

globalVariable = "I'm global";

document.getElementById('submit').onclick = function() {
  createWorker();
}


function createWorker() {
  // The text in the textarea is the function you want to run
  var fnText = document.getElementById('fnText').value;

  // You wrap the function to add a postMessage 
  // with the function result
  var workerTemplate = "\
function userDefined(){" + fnText +
    "}\
postMessage(userDefined());\
onmessage = function(e){console.log(e);\
}"

  // web workers are normally js files, but using blobs
  // you can create them with strings.
  var blob = new Blob([workerTemplate], {
    type: "text/javascript"
  });

  var wk = new Worker(window.URL.createObjectURL(blob));
  wk.onmessage = function(e) {
    // you listen for the return. 
    console.log('Function result:', e.data);
  }

}
<div>Enter a javascript function and click submit</div>
<textarea id="fnText"></textarea>
<button id="submit">
  Run the function
</button>

Ответ 3

Немного поздно, но, возможно, это поможет вам немного

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

    }   

Пример использования

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

Ответ 4

Вы можете использовать WebWorkers, чтобы изолировать свой код:

Создайте полностью отдельную и параллельную среду выполнения (т.е. отдельный поток или процесс или эквивалентную конструкцию) и выполните остальные эти шаги асинхронно в этом контексте.

Вот простой пример:

someGlobal = 5;

//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo) {
  return window.URL.createObjectURL(new Blob([foo], {
    type: 'text/javascript'
  }));
}

function protectCode(code) {
  var worker = new Worker(getScriptPath(code));
}

protectCode('console.log(someGlobal)'); // prints 10
protectCode('console.log(this.someGlobal)');
protectCode('console.log(eval("someGlobal"))');
protectCode('console.log(window.someGlobal)');

Этот код вернет:

Uncaught ReferenceError: someGlobal is not defined

undefined

Uncaught ReferenceError: someGlobal is not defined и

Uncaught ReferenceError: window is not defined

поэтому код теперь безопасен.

Ответ 5

Я дам технический ответ на ваш вопрос, по крайней мере, с одной возможностью. Используйте глобальное имя в качестве аргумента для этой функции:

someGlobal = 5;

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

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

Лучше, конечно, просто не использовать глобальные переменные когда-либо.

Ответ 6

Вы не можете ограничить область действия с помощью методов "вызов" или "применить", но вы можете использовать простой трюк, используя "eval" и scoping, чтобы скрыть любые конкретные глобальные переменные от вызываемой функции.

Причина этого в том, что функция имеет доступ к "глобальным" переменным, объявленным в области, которую сама функция объявляет. Таким образом, скопировав код метода и введя его в eval, вы можете существенно изменить глобальную область функции, которую вы хотите вызвать. Конечный результат по существу заключается в том, что несколько песочница может содержать часть кода javascript.

Вот пример полного кода:

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

Код, который получает eval'd, выглядит следующим образом:

var window = {};
var document = {};
(function displayTitle()
{
    alert(document.title);
})();

Ответ 7

Создайте локальную переменную с тем же именем. Если у вас есть глобальная переменная:

var globalvar;

В вашей функции:

function noGlobal(); {
    var globalvar;
}

Если функция ссылается на globalvar, она будет ссылаться на локальную.

Ответ 8

EDIT: этот ответ не скрывает переменные window.something. Но у него есть чистый способ запуска пользовательского кода. Я пытаюсь найти способ маскировать переменные окна

Вы можете использовать функцию javascript Function.prototype.bind(), чтобы привязать функцию, предоставленную пользователем, к настраиваемой переменной области по вашему выбору, в этой настраиваемой области вы можете выбрать, какие переменные должны делиться с пользовательской функцией и скрывать. Для пользовательских функций код сможет получить доступ к переменным, которые вы использовали, используя this.variableName. Вот пример, чтобы подробно остановиться на идее:

// A couple of global variable that we will use to test the idea
var sharedGlobal = "I am shared";
var notSharedGlobal = "But I will not be shared";

function submit() {
  // Another two function scoped variables that we will also use to test
  var sharedFuncScope = "I am in function scope and shared";
  var notSharedFuncScope = "I am in function scope but I am not shared";

  // The custom scope object, in here you can choose which variables to share with the custom function
  var funcScope = {
    sharedGlobal: sharedGlobal,
    sharedFuncScope: sharedFuncScope
  };

  // Read the custom function body
  var customFnText = document.getElementById("customfn").value;
  // create a new function object using the Function constructor, and bind it to our custom-made scope object
  var func = new Function(customFnText).bind(funcScope);

  // execute the function, and print the output to the page. 
  document.getElementById("output").innerHTML = JSON.stringify(func());

}

// sample test function body, this will test which of the shared variables   does the custom function has access to. 
/* 
return {
        sharedGlobal : this.sharedGlobal || null,
         sharedFuncScope : this.sharedFuncScope || null,
       notSharedGlobal : this.notSharedGlobal || null,
         notSharedFuncScope : this.notSharedFuncScope || null
 }; 
*/
<script type="text/javascript" src="app.js"></script>
<h1>Add your custom body here</h1>
<textarea id="customfn"></textarea>
<br>
<button onclick="submit()">Submit</button>
<br>
<div id="output"></div>

Ответ 9

Насколько мне известно, в Javascript любая переменная, объявленная вне функции, принадлежит глобальной области и поэтому доступна из любого места вашего кода.

Каждая функция имеет свою собственную область видимости, и любая переменная, объявленная внутри этой функции, доступна только из этой функции и любых вложенных функций. Локальная область в JavaScript создается только функциями, которая также называется функцией scope.

Помещение функции внутри другой функции может быть одной из возможностей, когда вы можете достичь уменьшенной области видимости (т.е. вложенной области)

Ответ 10

если вы говорите о функции, которая предоставляется вам при загрузке стороннего скрипта, вам не повезло. что поскольку область действия функции определена в исходном файле, в котором она определена. Конечно, вы можете связать ее с чем-то другим, но в большинстве случаев это сделает функцию бесполезной, если ей нужно будет вызывать другие функции или касаться каких-либо данных. внутри его собственного исходного файла - изменение его области действия действительно возможно только в том случае, если вы можете предсказать, к чему он должен иметь доступ, и иметь к нему доступ самостоятельно - в случае стороннего сценария, который касается данных, определенных внутри замыкания, объекта или функция, которая не входит в ваши возможности, вы не можете подражать тому, что может понадобиться.

если у вас есть доступ к исходному файлу, то это довольно просто - посмотрите на исходный файл, посмотрите, пытается ли он получить доступ к переменной, и отредактируйте код, чтобы он не смог.

но при условии, что у вас есть загруженная функция, и ей не нужно взаимодействовать ни с чем, кроме "окна", и по академическим соображениям вы хотите сделать это, вот один из способов - создать "песочницу" для ее воспроизведения. простая оболочка, которая исключает определенные элементы по имени

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>

Ответ 11

Я подтвердил ответ @josh3736, но он не оставил пример

Вот один, чтобы проверить это работает

parent.html

<h1>parent</h1>

<script>
abc = 'parent';
function foo() {
  console.log('parent foo: abc = ', abc);

}
</script>

<iframe></iframe>

<script>
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', function() {
  console.log('-calling from parent-');
  iframe.contentWindow.foo();
});
iframe.src = 'child.html';

</script>

child.html

<h1>
child
</h1>

<script>
abc = 'child';

function foo() {
  console.log('child foo: abc = ', abc);
}

console.log('-calling from child-');
parent.foo();
</script>

При запуске он печатает

-calling from child-
parent foo: abc = parent
-calling from parent-
child foo: abc = child

Как у потомка, так и у родителя есть переменная abc и функция foo. Когда дочерний элемент вызывает родительский foo эта функция в родительском элементе видит родительские глобальные переменные, а когда родительский элемент вызывает дочерний foo эта функция видит дочерние глобальные переменные.

Это также работает для Eval.

parent.html

<h1>parent</h1>

<iframe></iframe>

<script>
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', function() {
  console.log('-call from parent-');
  const fn = iframe.contentWindow.makeFn('(
      function() {
        return abc;
      }
  )');
  console.log('from fn:', fn());
});
iframe.src = 'child.html';

</script>

child.html

<h1>
child
</h1>

<script>
abc = 'child';

function makeFn(s) {
  return eval(s);
}
</script>

При запуске он печатает

-call from parent-
from fn: child

показывая, что он видел abc переменную abc не родительскую

примечание: если вы создаете iframes программно, они, кажется, должны быть добавлены в DOM, иначе они не будут загружаться. Так например

function loadIFrame(src) {
  return new Promise((resolve) => {
    const iframe = document.createElement('iframe');
    iframe.addEventListener('load', resolve);
    iframe.src = src;
    iframe.style.display = 'none';
    document.body.appendChild(iframe);  // iframes don't load if not in the document?!?! 
  });
}

Конечно, в дочернем элементе выше мы видели, что дочерний элемент может проникать в родительский элемент, поэтому этот код НЕ ПЕРЕДАЧАНО. Возможно, вам придется добавить некоторые вещи, чтобы скрыть различные способы доступа к родителю, если вы хотите, чтобы ребенок не мог вернуться, но, по крайней мере, в качестве начала вы, очевидно, можете использовать эту технику, чтобы придать коду другую глобальную область видимости.

Также обратите внимание, что, конечно, iframes должны находиться в том же домене, что и родительский.