
POCs zur Demonstration von CVE-2026-42167 in ProFTPD
Proof-of-Concept-Demonstrationen für CVE-2026-42167, eine SQL-Injection-
Schwachstelle in der mod_sql-Logging-Pipeline von ProFTPD, die es einem
nicht authentifizierten Angreifer ermöglicht, beliebiges SQL auszuführen – sowie Hintertür-Benutzer in
die FTP-Authentifizierungsdatenbank einzuschleusen oder Remote-Codeausführung auf dem
Datenbankhost zu erreichen.
Alle POCs sind PostgreSQL-spezifisch, aber die Hintertür-Benutzer-POCs funktionieren auch mit MySQL- und sqlite-Backends, mit einigen Änderungen an der injizierten Abfrage (was wir als Übung für den Leser offenlassen).
is_escaped_text()-Bypass in mod_sql SQLLog (CVE-2026-42167, CWE-89)Das mod_sql von ProFTPD protokolliert jeden FTP-Befehl über den SQLLog-/
SQLNamedQuery-Mechanismus. Beim Auflösen von Formatvariablen wie %U
(ursprünglicher Benutzername) oder %{basename} (Dateinamenskomponente) ruft das Framework
is_escaped_text() auf, um zu entscheiden, ob ein Escaping erforderlich ist – und überspringt
das Escaping vollständig für jeden Wert, der mit einem einfachen Anführungszeichen beginnt und endet
und keine internen einfachen Anführungszeichen enthält (z.B. '|| (SELECT 1) ||'). Die
Prüfung ist rein syntaktisch und
läuft auf roher, vom Angreifer kontrollierter Eingabe aus der FTP-Sitzung, sodass sie nicht
zwischen „von vertrauenswürdigem Code vorab maskiert" und „vom Angreifer so gestaltet, dass es
vorab maskiert aussieht" unterscheiden kann.
Das standardmäßig dokumentierte Muster umhüllt Formatvariablen mit einfachen Anführungszeichen für die SQL-Sicherheit:
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog * log_activity
SQLLog ERR_* log_activity
Wenn ein Angreifer einen Wert der Form '<payload>' liefert, erzeugt die Substitution
''<payload>'' im endgültigen SQL – die leeren String-Literale schließen
die umgebenden Anführungszeichen und das Payload wird als rohes SQL ausgeführt. Mit PostgreSQL
(PQexec) oder SQLite (sqlite3_exec) bedeutet die Unterstützung für gestapelte Abfragen, dass das
Payload eine vollständige INSERT-, UPDATE-, CREATE TABLE- oder
COPY TO PROGRAM-Anweisung sein kann.
Der anfällige Code befindet sich in contrib/mod_sql.c (dem gemeinsamen SQL-Framework),
nicht in einem spezifischen Backend, sodass alle SQL-Backends betroffen sind – aber die
Angriffsfläche hängt davon ab, wie der Administrator das Logging konfiguriert hat. Ein Server ist
ausnutzbar, wenn beides der folgenden Punkte zutrifft:
Der Administrator hat ein SQLNamedQuery INSERT (oder UPDATE) definiert, dessen Format-
zeichenfolge eine dieser vom Angreifer kontrollierbaren Variablen in einfachen Anführungszeichen
interpoliert – z.B. "'%U', '%m'". Die Variablen, die aus der Angreifereingabe stammen,
sind:
Der Bypass verwandelt den Logging-Pfad in eine Primitive für beliebiges SQL auf dem Backend. Die zwei Szenarien mit der größten Auswirkung:
Einen Hintertür-Benutzer mit beliebigen Berechtigungen einschleusen (Auth-Bypass).
Das SQL-Auth-Backend von ProFTPD liest Benutzernamen, Passwort-Hashes, uid, gid,
homedir und Shell aus derselben users-Tabelle, in die das Logging-INSERT nun
schreiben kann. Ein gestapeltes INSERT INTO users platziert ein vom Angreifer gewähltes Konto
– uid=0, homedir=/, Klartext-Passwort – und der Angreifer meldet sich
dann normal mit vollständigem Dateisystemzugriff über den FTP-Daemon an. Erreichbar
Pre-Auth über den %U + SQLLog ERR_*-Pfad oder Post-Auth über jede
vom Angreifer kontrollierte Variable, die an einen Befehl gebunden ist, den der Angreifer ausgeben kann
(z.B. %{basename} + SQLLog STOR). Funktioniert mit PostgreSQL und SQLite
(beide unterstützen gestapelte Abfragen).
Remote-Codeausführung auf dem Datenbankhost über COPY TO PROGRAM.
COPY (SELECT …) TO PROGRAM '<cmd>' von PostgreSQL führt <cmd> über eine
Shell auf dem Datenbankserver aus. Eine Stapelabfrage-Injektion, die
ausgibt, ermöglicht die Ausführung beliebiger OS-Befehle als postgres-
OS-Benutzer, was für die Exfiltration von Anmeldeinformationen, laterale Bewegung
und Persistenz ausreicht. Erreichbar über dieselben Trigger-Pfade wie im
Hintertür-Fall (Pre-Auth über oder Post-Auth über usw.).
PostgreSQL-spezifisch und erfordert, dass die ProFTPD-DB-Rolle über Superuser-
Rechte verfügt (die Voraussetzung für ) – üblich in Single-
Tenant-Bereitstellungen, in denen die Rolle der DB-Eigentümer ist.
Die POCs in diesem Repository zielen speziell auf PostgreSQL – den
stärksten Fall, da PQexec() gestapelte Abfragen unterstützt und COPY TO PROGRAM eine direkte Ausführung von OS-Befehlen ermöglicht. Der Bypass selbst befindet sich im
gemeinsamen SQL-Framework und betrifft alle Backends:
PQexec, RCE über
COPY TO PROGRAM.sqlite3_exec, läuft unter PRIVS_ROOT
im ProFTPD-Worker – die Hintertür-Injektion funktioniert auf die gleiche Weise; die RCE-
Primitive unterscheidet sich (kein COPY TO PROGRAM-Äquivalent, aber eine beschreibbare
users-Tabelle in einem SQL-Backend mit Root-Prozess ist mehr als genug).users-
Tabelle oder die Ausführung von OS-Befehlen aus dem einzelnen Logging-INSERT ist schwieriger.
Manipulation mit einer einzelnen Anweisung (Subquery-Exfiltration in einem VALUES-Platz,
zeitbasierte Blindinjektion, fehlerbasierte Blindinjektion) ist unkompliziert; das
in einen Schreibvorgang in eine andere Tabelle umzuwandeln, erfordert die Umgehung mehrerer
Hindernisse:
mod_sql_mysql ruft mysql_real_query() ohne
CLIENT_MULTI_STATEMENTS auf, daher wird ein abschließendes ; INSERT INTO users …
von der Verbindung abgelehnt.Innerhalb des PostgreSQL-Setups demonstrieren die POCs zwei repräsentative Trigger-Pfade. Andere Kombinationen aus der obigen Tabelle sind im Prinzip gleichwertig:
%U + USER. SQLLog ERR_* macht dies vollständig
ohne Authentifizierung möglich.%{basename} + STOR. Erfordert beliebige FTP-Anmeldeinformationen,
aber keine Berechtigungen über die Fähigkeit hinaus, eine Datei hochzuladen.setup/ — Automatisierte Umgebungseinrichtung. Klont den ProFTPD-Quellbaum,
der auf den Commit festgepinnt ist, gegen den diese Ergebnisse gemeldet wurden, baut den
Daemon mit mod_sql + mod_sql_postgres und stellt einen Docker-Compose-
Cluster (ProFTPD + PostgreSQL) mit Seed-Daten und der anfälligen SQLLog-
Konfiguration bereit.
pocs/ — Fünf Exploit-Skripte. Alle sind eigenständige Python-Skripte,
die nur die Standardbibliothek benötigen.
preauth_user_backdoor.py — Pre-Auth-%U-Trigger → Hintertür-Benutzer (uid=0,
homedir=/) in die Auth-DB injiziert. Keine Anmeldeinformationen, kein DB-Superuser
erforderlich. Dieser POC ist PostgreSQL-spezifisch, aber das Problem kann auch mit mysql- oder sqlite-Backend ausgenutzt werden.preauth_user_rce.py — Pre-Auth-%U-Trigger → RCE auf dem PostgreSQL-
Host über COPY TO PROGRAM. Keine Anmeldeinformationen. Erfordert, dass die ProFTPD-DB-Rolle
ein PostgreSQL-Superuser ist.postauth_stor_backdoor.py — Post-Auth--Trigger → Hintertür-
Benutzer injiziert. Erfordert einen beliebigen authentifizierten FTP-Benutzer. Dieser POC ist PostgreSQL-spezifisch, aber das Problem kann auch mit mysql- oder sqlite-Backend ausgenutzt werden.Voraussetzungen: Docker, Git, Python 3.10+ und uv.
cd setup
./setup.sh
Der erste Lauf klont den ProFTPD-Quellcode und baut den Server (~2–3 Min.). Nachfolgende Läufe verwenden den Build-Cache erneut und starten in Sekunden.
Wenn das Setup abgeschlossen ist, gibt es die vollständigen Umgebungsdetails aus (FTP- und DB- Endpunkte, Test-Anmeldeinformationen und einfügebereite POC-Befehle). Befolgen Sie diese Anweisungen, um die POCs auszuführen.
Zum Herunterfahren:
cd setup
./teardown.sh
| Var | Bedeutung |
|---|
%A | Passwortzeichenfolge für anonymen Login |
%J | Befehlsparameter (alles nach dem Verb) |
%S | Antwortnachrichten-Zeichenfolge (kann Angreifereingaben enthalten, die in Fehlern zurückgespiegelt werden) |
%U | ursprünglicher Benutzername aus USER (vor der Authentifizierung gesetzt, auch bei fehlgeschlagenem Login verfügbar) |
%d | Verzeichnisname (letzte Pfadkomponente) |
%l | RFC-1413-Ident-Antwort (vom Angreifer kontrollierbar, wenn er identd betreibt) |
%m | FTP-Methode/-Verb (der Angreifer wählt, welcher Befehl gesendet wird) |
%r | vollständiger FTP-Befehl (Verb + Argumente) |
%u | authentifizierter Benutzername |
%{basename} | Dateinamenskomponente des Pfadarguments, ohne Verzeichnispräfix |
%f, %F und %D sehen nach Angreiferkontrolle aus, lösen aber immer zu
absoluten Pfaden auf, die mit / beginnen, sodass sie die
is_escaped_text()-Anforderung „beginnt mit '" nicht erfüllen können.
Der Administrator hat dieses SQLNamedQuery an eine SQLLog-Direktive für einen
FTP-Befehl (oder eine Befehlsklasse) gebunden, den der Angreifer erreichen kann. Die weithin
dokumentierten Platzhalter SQLLog * und SQLLog ERR_* sind der breiteste Fall
und machen den %U-Pfad Pre-Auth: ERR_* feuert bei einem fehlgeschlagenen USER,
sodass keine Anmeldeinformationen erforderlich sind. Befehlsbezogene Direktiven wie
SQLLog STOR decken jeden authentifizierten Benutzer ab.
COPY TO PROGRAM%U%{basename}COPY TO PROGRAMusers-Tabelle oder in eine Datei auf der Festplatte zu schreiben.%{basename}postauth_stor_rce.py — Post-Auth-%{basename}-Trigger → RCE auf dem
PostgreSQL-Host. Erfordert einen beliebigen authentifizierten FTP-Benutzer und eine PostgreSQL-
Superuser-DB-Rolle.postgres_blind_dump.py — Pre-Auth-%U-Trigger → zeitbasierte Blind-
Extraktion der Auth-users-Tabelle. Verwendet keine gestapelten Abfragen, funktioniert
also bei Bereitstellungen, in denen die ProFTPD-DB-Rolle nur minimale
Berechtigungen hat (nur INSERT auf die Log-Tabelle) und die Hintertür-/RCE-
POCs oben würden fehlschlagen. Zieht jedes Byte jeder Spalte –
einschließlich der passwd-Spalte – indem es per Binärsuche ein Bit nach dem anderen
über pg_sleep() sucht. Spiegelt wider, was sqlmap automatisieren würde, handgeschrieben ohne
Abhängigkeiten von Drittanbietern.