The FortiGate SSL-VPN pot of gold. CVE-2024-21762 and CVE-2023-27997. 79 working exploit clients. 53 hardware SKUs. 55 FortiOS builds.
Research archive. Recovered as found. I did not write these files.
Released: 1 September 2026
This repository is the orchestrator, second-stage payloads, and operator listeners that sit on top of a per-SKU FortiGate SSL-VPN exploit catalog. The long-form story, counts, and hunting notes live in the series The SSL-VPN pot of gold on Abraxas Labs.
Do not run this material against any host unless you have explicit written permission from both the party hosting this repository and the owner of the target systems. The related CVEs were public and patched before this release. This tree is for research, detection, and historical analysis.
I found a private FortiGate SSL-VPN kit in the wild. The first drop was a directory of working clients: names like exp/exp_60E-v6.4.7.py and exp/exp_1000D-v7.4.0.py. Each file is a per-SKU, per-FortiOS memory-corruption client. Two public n-days, restamped across hardware and firmware until the folder looked like a parts catalog.
The gold is the offset matrix, not a new bug. That argument is in Phase 0 of the series. Phase 1 covers the two primitives as behavior. Phase 2 is the searchable SKU / train / CPU map. Phase 3 is hunting.
This repository is the rest of the same kit, not a second exploit family.
exp/ programs are the per-image clients already described in the series.shellcode/ files are the launcher, implant fetch, and config-steal listeners those clients were written to call.exp_*.py files never contained the implant. They only knew to (1) pull raw bytes from a “shellcode server” or (2) GET /s.js and eval it. Those missing pieces are here.The primitives:
| Family | CVE | Fortinet advisory | Public colour |
|---|---|---|---|
| A | CVE-2024-21762 | FG-IR-24-015 | Assetnote — Two Bytes is Plenty |
| B | CVE-2023-27997 | FG-IR-23-097 | Lexfo — XORtigate, Bishop Fox |
Both sit in CISA’s Known Exploited Vulnerabilities catalog. poc.py does not implement either CVE. It selects and wraps the two families already classified (chunked-write 21762; enc= heap overflow 27997).
.
├── poc.py # pocsuite3 launcher / fingerprint / process manager
├── s.js # Node second stage (config stealer)
├── send_cmd.py # callback handler for the stager path
├── init.sh # operator workstation package install
├── requirements.txt # pwntools
├── exp/
│ └── exp_<SKU>-v<FortiOS>.py
└── shellcode/
├── shellcode_server.py # length-prefixed blob pump
├── shellcode_arm # ~2.0 MB raw second stage
└── shellcode_amd64 # ~2.7 MB raw second stage
exp/index.html and shellcode/index.html are ordinary Python http.server directory listings. They only show that this tree was hosted as a web root at some point.
This copy of exp/ is the earlier catalog plus four extra clients: 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 orchestrator (pocsuite3)
│
├─ fingerprint portal ─────── VERSION_LIBS (hash → SKU + FortiOS)
├─ pick file ──────────────── exp/exp_<SKU>-v<ver>.py
│
├─ if client is "stager" kind (Family A ARM / some amd64)
│ start shellcode/shellcode_server.py
│ reads shellcode/shellcode_arm or shellcode_amd64
│ start send_cmd.py (wait for callback)
│ start config receiver on :9999
│ run chosen exp_*.py
│
└─ if client is "Node /s.js" kind (most Family B + many Family A)
serve s.js over HTTP
start config receiver on :9999
run chosen exp_*.py
→ firewall GET /s.js → eval → exfil
Pipeline in one line: identify → pick offset file → corrupt sslvpnd → pull implant → exfil /data/config/*.gz → archive on the operator side.
That last step is the point. A hit is unauthenticated code execution in sslvpnd on the firewall itself — TLS terminator, routing policy, VPN sessions, often LDAP/RADIUS bindings. FortiOS does not offer a useful /bin/sh. Public research and this kit both go through an internal command helper and/or /bin/node, which FortiOS actually ships. Treat a confirmed hit as compromise of the network edge, not a crashed daemon. See What a hit means.
poc.py — launcherOperator front end (pocsuite3 POCBase). It does not implement CVE-2024-21762 or CVE-2023-27997.
https://<target>/remote/login?lang=en (fallback GB2312)./sslvpn/js/login.js?q=<md5>.VERSION_LIBS (182 hash,SKU,version rows). That table is why the kit can choose among 50+ appliances without the operator naming the SKU. Same SKUs as the matrix, driven by a public SSL-VPN resource hash rather than SNMP or a management API.exp/exp_<SKU>-v<version>.py.s.js → kind 2 (Node loader)Shellcode Server → kind 1 (stager); then looks for arm vs amd64 in the scriptPopens the exp_*.py with host/port/callback arguments.