
Pré-authentification RCE dans ProFTPD via le contournement de is_escaped_text() de mod_sql (CVE-2026-42167)
mod_sqlAuteur : Van Glenndon Enad
Découverte originale : ZeroPath
Publié : 1er mai 2026
Gravité : Critique
Score CVSS v3.1 : 8.1
Vecteur CVSS v3.1 : CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Score CVSS v2 : 7.6
Vecteur CVSS v2 : CVSS2#AV:N/AC:H/Au:N/C:C/I:C/A:C
CWE : CWE-89 (Injection SQL), CWE-78 (Injection de commandes système)
CVE-2026-42167 est une vulnérabilité critique d'injection SQL avant authentification dans le module d'extension mod_sql de ProFTPD. Un défaut logique dans la fonction is_escaped_text() permet à un attaquant non authentifié de contourner l'échappement des caractères SQL en forgeant une commande USER dont la valeur satisfait une heuristique défectueuse de « déjà échappé ». Le SQL injecté est transmis directement à la base de données backend via PQexec(), qui prend en charge les requêtes empilées.
Lorsque le rôle de base de données ProFTPD est un superutilisateur PostgreSQL — une erreur de configuration courante dans les déploiements conteneurisés — l'injection atteint la directive COPY TO PROGRAM de PostgreSQL, ce qui entraîne une exécution de code à distance au niveau du système d'exploitation avant authentification en tant qu'utilisateur système postgres. Aucun identifiant, aucun accès préalable et aucune interaction utilisateur ne sont requis.
| Composant | Version |
|---|---|
| ProFTPD | ≤ 1.3.9 |
| Module | mod_sql + mod_sql_postgres |
| Version corrigée | 1.3.9a (publiée le 27 avril 2026) |
| Backend | PostgreSQL (RCE) ; MySQL / SQLite (contournement d'authentification uniquement) |
ProFTPD est un serveur FTP open source largement déployé. Selon Shodan, plus de 160 000 instances ProFTPD accessibles publiquement existent sur Internet. Le module mod_sql est couramment activé dans les panneaux de contrôle d'hébergement mutualisé, notamment cPanel, Plesk, DirectAdmin, Webmin et ISPConfig.
Le module mod_sql de ProFTPD prend en charge l'authentification et la journalisation d'activité basées sur SQL. Les chaînes de format de journal peuvent inclure des variables de substitution telles que %U (nom d'utilisateur), %r (hôte distant) et %m (commande FTP). Ces variables sont développées à l'exécution et insérées dans les requêtes SQL exécutées contre le backend configuré.
Une configuration vulnérable typique :
LoadModule mod_sql.c
LoadModule mod_sql_postgres.c
SQLEngine on
SQLBackend postgres
SQLAuthTypes Plaintext
SQLConnectInfo dbname@localhost dbuser dbpassword
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog * log_activity
SQLLog ERR_* log_activity
Dans cette configuration, la valeur fournie dans la commande FTP USER est substituée à %U et incluse directement dans une instruction SQL INSERT. Avant l'insertion, la valeur est transmise à is_escaped_text() pour déterminer si elle nécessite un échappement. Cette fonction contient un défaut logique critique.
is_escaped_text()Située dans contrib/mod_sql.c, la fonction applique l'heuristique suivante pour décider si une chaîne est « déjà échappée » :
static int is_escaped_text(const char *s) {
size_t slen = strlen(s);
/* Suppose que la chaîne est échappée si :
* 1. Elle commence par une apostrophe
* 2. Elle se termine par une apostrophe
* 3. Elle ne contient aucune apostrophe interne
*/
if (slen >= 2 &&
s[0] == '\'' &&
s[slen - 1] == '\'' &&
strchr(s + 1, '\'') == (s + slen - 1)) {
return TRUE; /* ignorer l'échappement */
}
return FALSE;
}
Lorsque cette fonction renvoie TRUE, sql_resolved_append_text() (ligne 777) insère la valeur brute, non échappée, directement dans la chaîne de requête. La valeur est ensuite exécutée par PQexec() dans contrib/mod_sql_postgres.c (ligne 1146), qui prend en charge les requêtes empilées (multi-instructions).
L'heuristique était probablement destinée à détecter les chaînes déjà entourées de délimiteurs de chaîne SQL. Cependant, elle ne tente pas de vérifier que le contenu interne est sûr — uniquement qu'aucune apostrophe supplémentaire n'est présente. Cela signifie que toute charge utile qui :
''$$ de PostgreSQL)...passera le contrôle et sera injectée telle quelle dans la requête SQL.
Client FTP ProFTPD PostgreSQL
│ │ │
│── USER '<payload>' ──▶ │ │
│ │ développer %U = '<payload>' │
│ │ is_escaped_text() = TRUE│
│ │ ignorer l'échappement │
│ │── INSERT INTO activity │
│ │ VALUES ('<payload>', │
│ │ ...) ──────────────▶ │
│ │ │ exécuter SQL empilé
│ │ │ COPY TO PROGRAM
│ │ │── commande shell ──▶ OS
| Exigence | Remarques |
|---|---|
mod_sql activé avec journalisation SQL | Doit journaliser une variable avant authentification telle que %U |
| Backend PostgreSQL | Requis pour le RCE via COPY TO PROGRAM ; MySQL/SQLite permettent toujours le contournement d'authentification |
| Le rôle de base de données est superutilisateur PostgreSQL | COPY TO PROGRAM est restreint aux superutilisateurs ou aux membres de pg_execute_server_program |
bash disponible sur l'hôte de la base de données | Requis pour la livraison du reverse shell via /dev/tcp |
| Accessibilité réseau | Le conteneur PostgreSQL doit pouvoir atteindre l'attaquant sur le port d'écoute |
La condition de superutilisateur est fréquemment remplie dans les déploiements conteneurisés où l'utilisateur de base de données ProFTPD est créé via POSTGRES_USER=... dans l'image Docker officielle de PostgreSQL, ou lorsqu'un administrateur accorde au rôle ProFTPD la propriété de la base de données.
Étape 1 : L'attaquant envoie une commande USER forgée (avant authentification, aucun identifiant requis)
│
▼
Étape 2 : ProFTPD développe %U avec la valeur contrôlée par l'attaquant
│
▼
Étape 3 : Contournement de is_escaped_text() — le SQL brut traverse sans échappement
│
▼
Étape 4 : PQexec() exécute la requête empilée contre PostgreSQL
│
▼
Étape 5 : COPY TO PROGRAM exécute la commande shell de l'attaquant en tant qu'utilisateur OS postgres
│
▼
Étape 6 : Reverse shell / exfiltration de fichiers livrés à l'attaquant
La charge utile d'injection est livrée via la commande FTP USER :