Do not judge me by name, I know that eval is evil, but there are reasons that I do so, and it will be very limited. Here's what: I want to create a safe space where I can run certain (and verified) bits of code and retrieve the result (if it matches what I expect). For security reasons, I wanted to cut it from all other areas (this space is requested for the result and it is assumed that it cannot export anything to the surrounding areas).
I came up with a solution that seems to work, which could also add context to the execution, but I'm not sure if it really is, or if there are security breaches on this system. Could you tell me if there is something wrong?
It actually creates local vars with the same name as global ones to prevent access to them. It also breaks down functions (I will add a function to save the ones I want to keep). The function is declared closest compared to the global ones in order to avoid the visibility of local areas that would be higher (a parameter for removing these specific local vars is planned).
function contextNoScope(root, context) { //I had to pass window as parameter for it to work properly for(var key in root){ if(key != '0' && key != 'window'){ eval('var ' + key + '=undefined;'); } } var window = undefined; var root = undefined; //root has to be forbidden too return function() { this.eval = function(str){ return eval(str); }; return this; }.call(context); }
APPLICATION:
(function(root){ function contextNoScope(root, context) {
(NOTE: If you check this code globally, be sure to add a special fix if you pass the global var to the context, because it will be masked like everyone else. Added also if you want to try passing the window as a context parameter by understandable reasons ..)
EDIT:
To understand more clearly what scripts are doing and what can solve my problem: I assume that JS does not have complete security in what can be called a "reliable source", since we are obviously talking about importing scripts here. By proxy, I mean that the start of the script will be verified, and security keys added to start.
Each script takes a specific variable from a given context and adds a property to it. Names are given here, meaning that we know in advance which var will be changed, and what the name and type of the added property is. In a sense, I can already check whether this was done or not, the problem here is to be sure that any code will not be able to intervene in events or in the DOM basically and will not delete or modify anything in this context , except for adding a property.
EDIT 2: please break it!
(I'm not an expert on all the rules of the site yet, correct me if this is not a way to edit it correctly: I decided not to modify the first code and not add a new version, even if it is a little longer.)
I am heading to a suitable sandbox for my decision made by better people than me, but since I am curious and want to continue to study, I am posting here an improved version of what I did because I would like to know how this can still be broken. So now I am looking for exact tricks that can break this system:
function contextNoScope(context) { var len, i, keys, val, window = root, isNaN = root.isNaN, console = root.console; if(!context){ context = {}; } keys = Object.getOwnPropertyNames(window); if(len = keys.length){ for(i = 0; i < len; i++){ val = keys[i]; //TODO: remove 'console' from the if (debug purpose) if(isNaN(val) && val != 'window' && val != 'eval' && val != 'isNaN' && val != 'console'){ eval('var ' + val + '=undefined;'); } } } isNaN = undefined; len = undefined; i = undefined; keys = undefined; val = undefined; eval('var root = undefined'); return function() { this.eval = function(str){ if(str){ str = str.toString(); //TODO: replace by more relevant regex (it currently throws error for words like 'evaluation') if(str.indexOf('constructor') > -1 || str.indexOf('__proto__') > -1 || str.indexOf('prototype') > -1 || str.indexOf('eval') > -1){ console.log('ERROR - str contains forbidden code "constructor", "__proto__", "eval", "prototype"', '\n' + str); return false; } try{ eval(str); return true; }catch(e){ console.log(e + '\nin:\n' + str); return false; } } console.log('ERROR - str is not defined: ', str); return false; }; return this; }.call(context); }
Some explanation for the changes:
- In the first version, I added a window as a parameter due to a basic error about when the word
var is evaluated, this is not necessary. But going through the window as another name ("root" here) seems easier. The whole function completes the same way as when using the first version. - added
getOwnPropertyNames to get also non-enumerable properties, but I know about the backward compatibility problem, this is not what I mean by the word βsystemβ for using an older browser (I already know that) - added masking of functions of local parameters or variables
- the test for
isNaN(val) here, so as not to evaluate as a variable the number-property of the window that reference i-frames eval('var root = undefined'); is a trick for var to be created at the moment the line is executed, and not at the very beginning, as it would be with the regular var- I know that functions declared in this space no longer have access to regular functions (try with
alert(1) , this will not work)