很久以前就有一个类似这样的问题。所以我拿出了一些旧代码并进行了修复。
它的实现基本上是利用了
with
关键字,并向其提供一个冻结的空对象。空对象的原型填充有
null
属性,其键与全局变量名(如
self
、
window
等)及其可枚举属性键相匹配;原型对象也被冻结了。然后在
with
语句中调用
eval
(如果我理解正确,这几乎与脚本在隐式的
with(window){}
块中运行的方式相同)。当您尝试访问
window
或其属性时,通过
with
块会将您重定向到在空对象中找到的 null 版本(具有相同的键)的原型对象中:
function buildQuarantinedEval(){
var empty=(function(){
var exceptionKeys = [
"eval", "Object",
"Number", "String", "Boolean", "RegExp", "JSON", "Date", "Array", "Math",
"this",
"strEval"
];
var forbiddenKeys=["window","self"];
var forbidden=Object.create(null);
[window,this,self].forEach(function(obj){
Object.getOwnPropertyNames(obj).forEach(function(key){
forbidden[key]=null;
});
Object.keys(obj).forEach(function(key){
forbidden[key]=null;
});
for(var key in obj){
forbidden[key]=null;
}
});
forbiddenKeys.forEach(function(key){
forbidden[key]=null;
});
exceptionKeys.forEach(function(key){
delete forbidden[key];
});
Object.freeze(forbidden);
var empty=Object.create(forbidden);
Object.freeze(empty);
return empty;
})();
return function(strEval){
return (function(empty,strEval){
try{
with(empty){
return eval(strEval);
}
}
catch(err){
return err.message;
}
}).call(empty,empty,strEval);
};
}
通过构建一个函数/闭包来设置,该函数/闭包评估一些表达式:
var qeval=buildQuarantinedEval();
qeval("'some expression'"); //evaluate
测试:
var testBattery=[
"'abc'","8*8","console","window","location","XMLHttpRequest",
"console","eval('1+1+1')","eval('7/9+1')","Date.now()","document",
"/^http:/","JSON.stringify({a:0,b:1,c:2})","HTMLElement","typeof(window)",
"Object.keys(window)","Object.getOwnPropertyNames(window)",
"var result; try{result=window.location.href;}catch(err){result=err.message;}; result;",
"parseInt('z')","Math.random()",
"[1,2,3,4,8].reduce(function(p,c){return p+c;},0);"
];
var qeval=buildQuarantinedEval();
testBattery.map(function(code){
const pad=" ";
var result= qeval(code);
if(typeof(result)=="undefined")result= "undefined";
if(result===null)result= "null";
return (code+pad).slice(0,16)+": \t"+result;
}).join("\n");
结果:
/*
'abc' : abc
8*8 : 64
console : null
window : null
location : null
XMLHttpRequest : null
console : null
eval('1+1+1') : 3
eval('7/9+1') : 1.7777777777777777
Date.now() : 1415335338588
document : null
/^http:/ : /^http:/
JSON.stringify({: {"a":0,"b":1,"c":2}
HTMLElement : null
typeof(window) : object
Object.keys(wind: window is not an object
Object.getOwnPro: can't convert null to object
var result; try{: window is null
parseInt('z') : parseInt is not a function
Math.random() : 0.8405481658901747
[1,2,3,4,8].redu: 18
*/
注解:当窗口的某些属性在初始化/创建我们隔离的求值函数之后定义时,该技术可能会失败。过去,我注意到有些属性键直到访问该属性后才进行枚举,此后
Object.keys 或 Object.getOwnPropertyNames 最终能够抓取它们的键。另一方面,该技术也可以非常积极地阻止您不想被阻止的对象/函数(例如像 parseInt 这样的示例); 在这些情况下,您需要手动将您需要的全局对象/函数添加到 exceptionKeys 数组中。
附加注意事项:所有操作的效果取决于掩码与窗口对象的属性键匹配的程度。每当您向文档添加一个元素并为其分配新的ID时,就会向全局窗口对象中插入一个新属性,这可能允许我们的“攻击者”抓取它并打破我们设置的隔离/防火墙(即从那里访问element.querySelector,然后是window obj)。因此,掩码(即变量forbidden)需要不断更新,可以使用watch方法或每次重建;前者与掩码必须具有冻结接口的必要性相冲突,而后者则有点昂贵,需要枚举每个评估的窗口键。
就像我之前说的那样,这主要是我曾经在工作中放弃的旧代码,在短时间内快速修复。因此,它绝不是经过彻底测试的。我将留给您去测试。
还有一个jsfiddle链接
Ctrl
+Shift
+I
)来执行任何代码。 - Aadit M Shaheval
执行的代码不来自用户,而是“来自任何其他地方...”。同时,我会尽力防止这种情况发生! - heinobvar u = window; u["al"+"ert"]("hello");
- heinob