前言
原型链污染是一个关于前端js的相关漏洞,原型链污染可拆分为两部分,什么是原型链,又如何进行污染?
什么是原型链
在此之前需要了解js对象的概述以及对象的创建。(大佬文章学习JavaScript这一篇就够了_轻松的小希的博客-CSDN博客_javascript学习)
在js语言中,每一个实例对象都有一个私有属性(proto)指向它的构造函数的原型对象(prototype)
而该原型对象又有一个自己的原型对象,就像套娃一样。知道原型对象为object,它是几乎所有的javascript中的对象的祖宗。所以它是没有原型的,或者说它的原型为null。到这为止,整条原型链结束。
继承
实例对象和原型对象是有继承关系的。当试图访问一个对象的属性时,它会先在该对象中搜索,如果该对象没有此属性,就会在该对象的原型中去搜索。以此类推,如果直到object原型对象都没有这个属性,就会返回undefined。来通过实例说一下:
var ad = function(){
this.a = 1;
this.b = 2;
}
var we = new ad(); //从一个函数里创建一个对象we
ad.prototype.b = 3;
ad.prototype.c = 4;//在ad函数的原型对象中定义属性
console.log(we.a);//we对象中有a属性,为一。
console.log(we.b);//we对象也有b属性,为2.
//原型中也有b属性,但是不会被访问到。也想当于重写。
console.log(we.c);//we对象没有c属性,所以在原型中找,为4
console.log(we.d);//d不是we对象的属性,继续看,d也不是
//we.[[Prototype]]中的属性,继续,d也不是we.[[Prototype]].[[Prototype]]
//中的属性,到此结束,返回undefined
看运行结果
看两个原型链的例子
var a = {a:1};//通过语法创建对象
//原型链:o ---> Object.prototype ---> null
var b = ["X","Y","Z"];
//原型链:a ---> Array.prototype ---> Object.prototype ---> null
如何进行污染
我们首先要知道prototype和_proto_有什么关系?我们来以定义构造函数的方式来定义一个类
function XiLitter(){
this.age = 19;
}
var a = new XiLitter();
说白了,就是XiLitter.prototype等价于a._proto_。就是一个对象的_proto_属性指向所在的类的prototype属性。
原理
如果我们能够控制改变原型对象的属性。比如对于语句object[a][b]=c 我们可以将a设置为_proto_,然后在原型中设置一个属性b,并赋值于c,那么所有继承该原型对象的实例对象都会在本身不拥有b属性的情况下拥有b属性,且值为c。看个例子
ob1 = {"a":123,"b":456};//创建一个对象ob1
ob1.__proto__.ab = "123456";//添加原型属性ab并赋值123456
console.log(ob1.ab);
ob2 = {"a":1234,"b":5678};//创建一个对象ob2
console.log(ob2.ab);
看输出结果
说到这里,基本的原理都了然。
如何利用
只有在以下三种情况才可以进行原型链污染攻击。
对象递归合并
按路径定义属性
对象克隆
借助ctfshow中的web338题实例如何利用原型链进行攻击。
看题目代码关键部分
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var flag='flag_here';
var secert = {};
var sess = req.session;
let user = {};
utils.copy(user,req.body);
if(secert.ctfshow==='36dboy'){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
});
看代码,只要让secert.ctfshow==='36dboy'就能输出flag。最主要的漏洞代码还是copy的一个递归调用函数
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
它会for循环遍历object2中的键,如果这个键名在object1和object2中都存在,那么就调用copy函数,否则将object2的key赋值给object1。我们可以控制object2,如果object2中的key设置为_proto_,就可以原型链污染了。我们将object2赋值为
{"__proto__":{"ctfshow":"36dboy"}}
因为无论object1还是object2都是有原型的,所以当key为__proto__时,if语句返回true,执行copy函数。又套进去了。。。此时key就为ctfshow了,但是object1中没有ctfshow,所以,if语句返回false,执行赋值语句,则object的原型[ctfshow]就成功赋值为36dboy。
这里有必要多说几句,当__proto__作为键名时,就会进入下一个copy函数,此时就不是object1和object2了,而是object1.__proto__和object2.__proto__了。这就导致了键名__proto__变成了原型了,而ctfshow就成为了原型中的属性,成功污染原型链。所有实例对象都有这个属性,就能够满足secert[ctfshow]=36dboy,成功得出flag。(个人理解,若有错误,感谢指正)
注意:
我们想让__proto__为键名就必须要json语法格式,不然的话,__proto__就会识别为原型而不是键名,所以在key遍历时也只有ctfshow这个键名了。
结语
原型链污染的题目练的很少,在此只记录一下什么是原型链污染。遇到相关题目再继续巩固。
相关连接:
Node.js 原型污染攻击的分析与利用 - 先知社区
深入理解JavaScript Prototype污染攻击 - FreeBuf网络安全行业门户
版权归原作者 XiLitter 所有, 如有侵权,请联系我们删除。