XSS 前端防火墙?无懈可击的钩子
作者:网络转载 发布时间:[ 2014/7/4 15:09:00 ] 推荐标签:XSS 安全测试工具
靠,这不算,这只是我们测试而已。现实中谁会放在全局变量里呢,这年头不套一个闭包的脚本都不好意思拿出来。
好吧,我还是放闭包里,这总安全了吧。看你怎么隔空取物,从我闭包里偷出来。
(function() {
// 保存上级接口
var raw_fn = Element.prototype.setAttribute;
...
})();
不过,真要偷出来,那是没问题的!
这个变量用到的地方是:
raw_fn.apply(this, arguments)
这可不是一个原子操作,而是调用了 Function.prototype.apply 这个全局函数。神马。。。这。是真的,不信你可以试试!
不用说,你也懂了。我还是说完吧:我们可以重写 apply,然后随便给某个元素 setAttribute 下,可以窃听到钩子传过来的 raw_fn 了。
Function.prototype.apply = function() {
console.log('哈哈,得到原始接口了:', this);
};
document.body.setAttribute('a', 1);
Run
这也太贱了吧,不带这样玩的。可人家能用这招绕过你,又怎样。
你会想,干脆把 Function.prototype.apply 也提前保存起来得了。然后一番折腾,你会发现代码变成 apply.apply.apply.apply...
毕竟,apply 和 call 已是底层了,没法再 call 自己了。
这可怎么办。显然不能再用 apply 或 call 了,但不用它们没法把 this 变量传进去啊。回想下,有哪些方法可以控制 this 的:
obj.method()
method.call(obj)
貌似也这两类。排除了第二种,那只剩古老的用法了。可是我们已经重写了现有的接口,再调用自己那递归溢出了。
但是,我们可以给原始接口换个名字,不可以避免冲突了:
(function() {
// 保存上级接口
Element.prototype.__setAttribute = Element.prototype.setAttribute;
// 勾住当前接口
Element.prototype.setAttribute = function(name, value) {
// 额外细节实现 ...
// 向上调用
this.__setAttribute(name, value);
};
})();
Run
|
这样倒是甩掉 apply 这个包袱了,但是无论取『__setAttribute』,还是换成其他名字,人家知道了,照样可以拿出原始接口。所以,我们得取个复杂的名字,好每次还都不一样:
(function() {
// 取个霸气的名字
var token = '$' + Math.random();
// 保存上级接口
Element.prototype[token] = Element.prototype.setAttribute;
// 勾住当前接口
Element.prototype.setAttribute = function(name, value) {
// 额外细节实现 ...
// 向上调用
this[token](name, value);
};
})();
Run
|
现在,你完全不知道我把原始接口藏在哪了,而且用 this[token](...) 这个巧妙的方法,同样符合刚才列举的第一类用法。
问题似乎。。。解决了。但,总感觉有什么不对劲。。。人家不知道变量藏哪了,难道不可以找吗。把 Element.prototype 遍历下,一个个找过去,不相信会找不到:
for(var k in Element.prototype) {
console.log(k);
if (k.substr(0,1) == '$') {
console.error('楼上的,你这名字那么猥琐,敢露个面吗');
console.error(Element.prototype[k]);
}
}
Run
|
取了个这么拉风的名字,象是黑暗中的萤火虫,瞬间给揪出来了。你会说,为什么不取个再隐蔽点的名字,甚至还可以冒充良民,把从来不用的方法给替换了。
不过,无论想怎么躲,都是徒劳的。有无数种方法可以让你原形毕露。除非 —— 根本不能被人家枚举到。
属性隐身术
如果没记错的话,主流 JavaScript 里好像还真有什么叫 enumerable、configurable 之类的东西。把它们搬出来,看看能不能赋予我们隐身功能?
马上试试:
// 嘘~ 劳资要隐身了
Object.defineProperty(Element.prototype, token, {
value: Element.prototype.setAttribute,
enumerable: false
});
Run
神奇,红红的那坨字果然没出现。看来真的隐身了!
到此,原函数泄露的问题,我们算是搞定了。
不过暂时还不能松懈,为什么?连 apply 都能被山寨,那还有什么可以相信的!那些正则表达式的 test 方法、字符串的大小写转换、数组的 forEach 等等等等,都是可以被改写的。
要是人家把 RegExp.prototype.test 重写了,并且总是返回 false,那么我们的策略判断完全失效了。
所以,我们得重复上面的步骤,把这些运行时要用到的全局方法,都得随机隐匿起来。
锁死 call 和 apply
不过,隐藏一个还好,大量的代码都用这种 Geek 的方式,显得很是累赘。
既然能有隐身那样神奇的魔法,难道没有其他类似的吗?事实上,Object.defineProperty 里还有很多有意思的功能,除了让属性不可见,还能不可写、不可删等等。
可以让属性不可写?太好了,不如干脆把 Function.prototype.call 和 apply 都事先锁死吧,反正谁会无聊到重写它们呢。
Object.defineProperty(Function.prototype, 'call', {
value: Function.prototype.call,
writable: false,
configurable: false,
enumerable: true
});
// apply 也一样
马上看看效果:
Function.prototype.call = function() {
alert('hello');
};
console.log(Function.prototype.call);
果然还是
function call() { [native code] }
Run
|
现在,我们大可放心的使用 call 和 apply,再也不用鼓捣那堆随机属性了。
不过这种随机+隐藏的属性,今后还是有用武之地的,常常用来给公开的对象做个秘密的记号,所以没有白折腾。
到此,我们终于可以松口气了。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11