Journée javascript semi-sandboxing

Contexte : Je travaille sur un cadre / bibliothèque à utiliser pour un site spécifique en coordination avec greasemonkey / userscripts. Ce framework / bibliothèque permettra un support complémentaire. La façon dont il fonctionnera est que les registres d'addon contenant les pages requises, les ressources, les écrans et la bibliothèque de la bibliothèque attendent que tous les critères soient remplis pour appeler la fonction load() l'addon.

Le problème : dans cette liste de «choses requises», je souhaite que addon devs puisse spécifier javascript (en tant que chaîne) pour être évalué comme une «ressource requise». Par exemple 'document.getElementById("banana")' . Ce que je veux faire, c'est semi-sandbox, l'évaluation de la «ressource requise», de sorte que l'évaluation peut accéder à la fenêtre et les objets DOM mais ne peut pas les modifier directement. J'aimerais également que eval et evalJS soient inaccessibles à partir du bac à sable.

Exemples :

  • document.getElementById("banana") -> valide
  • document.getElementById("apple).id = "orange" -> invalide
  • window.grape -> valide
  • window.grape = 'potato' -> invalide
  • (someObj.applesCount > 0 ? 'some' : 'none') -> valide

Ce que j'ai jusqu'ici :

 function safeEval(input) { // Remove eval and evalJS from the window: var e = [window.eval, window.evalJS], a; window.eval = function(){}; window.evalJS = function(){}; try { /* More sanition needed before being passed to eval */ // Eval the input, stuffed into an annonomous function // so the code to be evalued can not access the stored // eval functions: a = (e[0])("(function(){return "+input+"}())"); } catch(ex){} // Return eval and evalJS to the window: window.eval = e[0]; window.evalJS = e[1]; // Return the eval'd result return a; } 

Notes :
C'est un Greasemonkey / userscript. Je n'ai pas d'accès direct pour modifier le site, ou c'est javascript.
L'entrée pour safeEval() peut être n'importe quel javascript valide, que ce soit une requête DOM, ou des évaluations simples tant qu'elle ne modifie pas l'objet fenêtre ou DOM.

Il n'y a aucun moyen absolu d'empêcher un utilisateur final ou un développeur addon d'exécuter un code spécifique en JavaScript. C'est pourquoi les mesures de sécurité dans un langage open source comme JavaScript sont dignes d'être infaillibles (comme c'est seulement efficace contre les imbéciles).

Cela étant dit, construisons une couche de sécurité de sandbox pour empêcher les développeurs inexpérimentés de briser votre site. Personnellement, je préfère utiliser le constructeur de Function sur eval pour exécuter le code utilisateur pour les raisons suivantes:

  1. Le code est enveloppé dans une fonction anonyme. Par conséquent, il peut être stocké dans une variable et appelé autant de fois que nécessaire.
  2. La fonction existe toujours dans la portée globale. Par conséquent, il n'a pas accès aux variables locales dans le bloc qui a créé la fonction.
  3. La fonction peut être transmise arbitrairement nommé paramètres. Par conséquent, vous pouvez exploiter cette fonctionnalité pour passer ou importer des modules requis par le code utilisateur (p. Ex. jQuery ).
  4. Plus important encore, vous pouvez définir un pointeur personnalisé et créer des variables locales nommées window et document pour empêcher l'accès à la portée globale et au DOM. Cela vous permet de créer votre propre version du DOM et de la transmettre au code utilisateur.

Notez cependant que même ce modèle présente des inconvénients. Plus important encore, il ne peut qu'empêcher un accès direct à la portée mondiale. Le code utilisateur peut encore créer des variables globales en déclarant simplement les variables sans var , et le code malveillant peut utiliser des hacks comme la création d'une fonction et l'utiliser est this pointeur pour accéder à la portée globale (le comportement par défaut de JavaScript).

Examinons donc un code: http://jsfiddle.net/C3Kw7/

Si tout ce que vous voulez est un getter simple, programmez-vous au lieu d'essayer n'importe quoi.

 function get(input) { // if input is a string, it will be used as a DOM selector // if it is an array (of strings), they will access properties of the global object if (typeof input == "string") return document.querySelector(input); else if (Array.isArray(input)) { var res = window; for (var i=0; i<input.length && typeof input[i] == "string" && res; i++) res = res[input[i]]; return res; } return null; } 

Vous pouvez faire quelque chose comme ceci: http://jsfiddle.net/g68NP/

Le problème est que vous devrez ajouter beaucoup de code pour protéger chaque propriété, chaque méthode native, etc. La viande du code se résume vraiment à utiliser __defineGetter__ , dont le support est limité. Comme vous ne l'utilisez probablement pas sur IE, vous devriez être bien.

EDIT: http://jsfiddle.net/g68NP/1/ Ce code rendra toutes les propriétés en lecture seule. L'utilisation de hasOwnProperty() peut ou non être souhaitable.

Dans le cas où JSFiddle descend:

 function safeEval(input) { // Remove eval and evalJS from the window: var e = [window.eval, window.evalJS, document.getElementById], a; window.eval = function(){}; window.evalJS = function(){}; document.getElementById = function (id) { var elem = (e[2]).call(document, id); for (var prop in elem) { if (elem.hasOwnProperty(prop)) { elem.__defineGetter__(prop, function () { return (function (val) { return val; }(elem[prop])); }); } } return elem; }; try { /* More sanition needed before being passed to eval */ // Eval the input, stuffed into an annonomous function // so the code to be evalued can not access the stored // eval functions: a = (e[0])("(function(){return " + input + "}())"); } catch(ex){} // Return eval and evalJS to the window: window.eval = e[0]; window.evalJS = e[1]; document.getElementById = e[2]; // Return the eval'd result return a; } 

Je sais que c'est un ancien message, mais je veux simplement partager une version améliorée de la solution Aadit M Shah , qui semble vraiment être un bac à sable sans aucune façon d'accéder à la fenêtre (ou aux enfants de la fenêtre): http://jsfiddle.net/C3Kw7 / 20 /

 // create our own local versions of window and document with limited functionality var locals = { window: { }, document: { } }; var that = Object.create(null); // create our own this object for the user code var code = document.querySelector("textarea").value; // get the user code var sandbox = createSandbox(code, that, locals); // create a sandbox sandbox(); // call the user code in the sandbox function createSandbox(code, that, locals) { code = '"use strict";' + code; var params = []; // the names of local variables var args = []; // the local variables var keys = Object.getOwnPropertyNames( window ), value; for( var i = 0; i < keys.length; ++i ) { //console.log(keys[i]); locals[keys[i]] = null; } delete locals['eval']; delete locals['arguments']; locals['alert'] = window.alert; // enable alert to be used for (var param in locals) { if (locals.hasOwnProperty(param)) { args.push(locals[param]); params.push(param); } } var context = Array.prototype.concat.call(that, params, code); // create the parameter list for the sandbox //console.log(context); var sandbox = new (Function.prototype.bind.apply(Function, context)); // create the sandbox function context = Array.prototype.concat.call(that, args); // create the argument list for the sandbox return Function.prototype.bind.apply(sandbox, context); // bind the local variables to the sandbox }