原型链污染

前言

原型链污染是一个关于前端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

看运行结果

原型链污染_第1张图片

看两个原型链的例子

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网络安全行业门户

你可能感兴趣的:(#,基础漏洞,javascript,前端,web安全,开发语言)