SeriesN-dayCVE-2024-21762CriticalPublic

The SSL-VPN pot of gold · Phase 4

The kit on GitHub

Appendix. The recovered tree is public. What each file is, where it lives, and how the launcher, clients, and missing second stage fit together.

Name
The kit on GitHub
Series
The SSL-VPN pot of gold
Phase
4 · Appendix
Type
N-day analysis
CVE
CVE-2024-21762
CVE Risk
critical
Disclosure Status
public
Vendor
Fortinet
Affected
FortiGate SSL-VPN kit released as found
Published
01 Sept 2026
Updated
01 Sept 2026
Tags
n-day, fortinet, ssl-vpn, github, appendix

The folder is public

Fortigate-SSL-VPN-Exploit-Kit on github.com/abraxas (catalog). Recovered as found. I did not write these files. The related CVEs were public and patched before this release.

This page is a map of the tree, not a usage guide. Do not run it against anything you do not own. Phase 0 is the find. Phase 1 is the two doors. Phase 3 is how you hunt.

The first drop was the exp_*.py clients. The GitHub tree is the rest of the same kit: orchestrator, listeners, and the second stage those clients were written to fetch. Not a third CVE. The objective is explicit now: steal FortiOS config from /data/config/*.gz.

Where the files are

Path What it is GitHub
poc.py pocsuite3 launcher / fingerprint / process manager. Does not implement either CVE. poc.py
s.js Node second stage. Config stealer. s.js
send_cmd.py Callback handler for the stager path (port 7777). send_cmd.py
init.sh Operator workstation package install. Not used against the firewall. init.sh
requirements.txt One line: pwntools. requirements.txt
exp/ Per-SKU, per-FortiOS clients. exp_<SKU>-v<FortiOS>.py. exp/
shellcode/shellcode_server.py Length-prefixed blob pump (port 8888 in this copy). shellcode_server.py
shellcode/shellcode_arm ~2.0 MB raw second stage. shellcode_arm
shellcode/shellcode_amd64 ~2.7 MB raw second stage. shellcode_amd64

exp/index.html and shellcode/index.html are ordinary Python http.server listings. They only show this tree was hosted as a web root at some point.

This copy of exp/ is the catalog from Phase 0 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.

How the pieces wire

Plain text
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 → zip on the operator side.

That last step is the point. A hit is unauthenticated code execution in sslvpnd on the firewall itself. Treat it as compromise of the network edge.

Literal IPv4 addresses in this copy were replaced with role labels. Ports were left as recovered.

Label Role
TARGET_HOST FortiGate. Never hardcoded; rhost at run time.
ATTACKER_HOST Operator workstation. Default bind / lhost.
C2_HOST Baked callback inside this copy of s.js. Not the lhost passed into the client.
Port Role
7777 Callback / send_cmd.py
8888 Shellcode server
9999 Config receiver
File-by-file (collapsed)

poc.py. Operator front end (pocsuite3 POCBase). Fetches /remote/login, pulls the cache-buster hash from /sslvpn/js/login.js?q=<md5>, looks it up in VERSION_LIBS (182 hash,SKU,version rows), then resolves exp/exp_<SKU>-v<version>.py. 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. It classifies a client by reading its source (s.js → Node loader; "Shellcode Server" → stager) and starts the matching helpers. Unknown hash → stop. Hash matches, file missing → "no exp."

s.js. The file Family B / Node Family A clients fetch after the command helper. Not a reverse shell. Hard-coded callback C2_HOST:9999. Reads /data/config/, takes non-symlink *.gz FortiOS config objects, deflates them, ships each over a small length-prefixed TCP protocol. That is why poc.py also listens on 9999.

send_cmd.py. Stager-path callback on 7777. Same goal as s.js (steal config), different transport. On connect it asks the implant to cat every *.gz toward port 9999.

shellcode/shellcode_server.py. Byte pump. Bind, accept one connection, send a 4-byte little-endian length, send shellcode_<arch>. The tiny stubs inside the ARM exp_*.py files only fetch these blobs.

shellcode_arm / shellcode_amd64. ~2.0 MB and ~2.7 MB. Size says packed or static payload, not a few dozen instructions. Treat them as the implant for the non-Node path.

init.sh. Installs build-essential, binutils-arm-linux-gnueabihf, gcc-aarch64-linux-gnu on a Debian-like attack box. Consistent with an author who compiles stagers rather than only shipping Python.

requirements.txt. pwntools. poc.py also wants pocsuite3, python-dotenv, mmh3, and requests.

What this changes

The first write-up stopped at "get a callback." This tree makes the objective explicit:

  1. Steal FortiOS config from /data/config/*.gz. Credential and topology compromise (VPN users, routes, secrets in those objects).
  2. IOC set is concrete. Portal recon, first-stage URIs, outbound /s.js, outbound TCP to 7777 / 8888 / 9999. See Phase 3.
  3. VERSION_LIBS in poc.py is a ready-made "which SKU did they think this hash was" list.
  4. The kit is a complete steal-config pipeline, not a bag of unconnected PoCs.

Nothing in the parent directory implements a third CVE. Counts and train alignment stay in Phase 0 and Phase 2.

Notice

These files were recovered in the wild during independent security research. Released as found, after the related CVEs were disclosed and patched. Research / educational use only.