0%

前端安全7-原型链污染(笔记)

前言

以前一直以为原型链污染是后端污染一下那些渲染的中间件什么的,看了google ctf那题,发现前端也可以用这种思路

有几章我跳过了,因为感觉都是些历史漏洞,就不太想再发文章去记录,但是也是很有知道的必要
Universal XSS
Mutation XSS

原型链

总感觉自己讲不清楚,还是去看别人写的吧

1
2
3
4
5
6
7
let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)

o3 = {}
console.log(o3.b)

当用JSON.parse可以把proto当为纯键值。
调试分析了一下,发现在第二层的时候会判断__proto__是否在对象中,对于target来说是原型链存在,对于source来说是普通的键值,就是{'b':2}
当然也可以用
1
let o2 = JSON.parse('{"a": 1, "constructor": {"prototype":{"b": 2}}}')

在控制台输入
1
2
3
obj = {}
obj.__proto__ == obj.constructor.prototype
//true

constructor 和 prototype

这时候会引出一个问题,就是经常会看到,一会是constructor.prototype,一会又是prototype.constructor,这时候就很容易搞混
可以参考这篇文章,我感觉我自己都有点迷糊
https://wangdoc.com/javascript/oop/prototype

1
2
3
4
5
6
7
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

1
Animal.constructor.prototype==cat1.constructor.prototype//false

同样是constructor.prototype,Animal是Function,cat1是Object
cat1.constructor指向他的构造函数,也就是Animal
Animal.constructor也是指向他的构造函数,但是是Function,所以下面的就会是true

1
2
3
4
Animal.constructor.prototype == Function.prototype
true
cat1.constructor.prototype == Animal.prototype
true

__proto__可以用来访问对象的原型,也就是prototype

1
2
3
4
cat1.__proto__.__proto__ == Object.prototype
true
cat1.__proto__ == Animal.prototype
true

原理这些还是自己去找资料吧,感觉我讲的也不清晰

Prototype pollution script gadgets

https://github.com/BlackFan/client-side-prototype-pollution

防御

过滤字符串过滤

1
2
3
if (key === "constructor" || key == '__proto__' || key == "prototype") {
throw new Error("No pollution")
}

创建一个空对象

1
2
3
var obj = Object.create(null)
obj['__proto__']['a'] = 1
// TypeError: Cannot set property 'a' of undefined

冻结prototype

1
2
3
4
5
Object.freeze(Object.prototype)
var obj = {}
obj['__proto__']['a'] = 1
var obj2 = {}
console.log(obj2.a)

不会报错,只是不能被修改,可能会增加debug的难度

nodejs

通过--disable-proto关掉prototype
官方文档

隐形的前端gadget

可以通过更改前端的API的一些变量

1
2
3
4
5
Object.prototype.body = 'a=1'
Object.prototype.method = 'POST'
fetch('https://example.com', {
mode: 'cors'
})

例如上面这种,就会把本来应该发送GET请求的代码,变成发送POST

有时候有些条件可以让你直接把fetch给覆盖了,这样他的代码都会走你的假fetch,这也是爬虫里面经常用到的技巧
例如下面的代码,参考 https://mp.weixin.qq.com/s/H4oQvqZmWS4af1VF1nAzkA

1
2
3
4
5
6
7
8
(function() {
var stringify = JSON.stringify;
JSON.stringify = function(params) {
console.log("Hook JSON.stringify ——> ", params);
debugger;
return stringify(params);
}
})();
1
2
3
4
5
6
7
8
9
(function () {
var open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async) {
if (url.indexOf("rnd") != -1) {
debugger;
}
return open.apply(this, arguments);
};
})();

把原来的API备份一份,然后在把原来的API给覆盖了