RCE en pgAdmin 4 vía Import/Export, corregido en 9.19 (CVE-2026-116241)
pgAdmin 4 antes de 9.19 deja a cualquier usuario de Import/Export ejecutar un comando TO PROGRAM de psql y permite intentos ilimitados de adivinar la contraseña del administrador; corregido en 9.19.
Resumen
- La herramienta Import/Export Data de pgAdmin 4 ejecuta un comando del sistema operativo de un atacante en el host de pgAdmin cuando una exportación de consulta manipulada supera su guarda de paréntesis: CVE-2026-116241, CVSS v3.1 9.9 Critical según el CNA de PostgreSQL. El permiso de la herramienta se concede a los no administradores por defecto.
- La misma publicación corrige un segundo crítico, CVE-2026-116238: cuatro evasiones del límite de intentos fallidos de inicio de sesión permiten a un atacante no autenticado adivinar la contraseña del administrador sin límite en modo servidor. Yonghwa Lee de Xint by Theori reportó la inyección.
- Las 14 fallas del lote, publicadas el 30 de octubre de 2026, están todas corregidas en pgAdmin 4 9.19, publicado el 29 de octubre de 2026. Todas las versiones anteriores, hasta las primeras publicaciones de la herramienta, están afectadas.
- No se reporta exploit público ni explotación, y ninguno de los dos críticos está en el catálogo KEV (Known Exploited Vulnerabilities) de CISA al 31 de octubre de 2026. Actualice a 9.19 ahora; los cambios de bloqueo del modo servidor requieren planificación y están listados en «¿Cómo lo corrijo?».
¿Estoy afectado?
Para la inyección de comandos, CVE-2026-116241, todas estas condiciones deben cumplirse:
- Usted ejecuta pgAdmin 4 en cualquier versión anterior a 9.19, en cualquier despliegue: modo servidor, modo escritorio o contenedor.
- Un usuario en el que no confía plenamente puede usar la herramienta Import/Export Data en alguna conexión de servidor. El permiso
tools_import_export_datase concede a los no administradores por defecto. - El rol de base de datos de ese usuario en el servidor destino puede ejecutar
SET standard_conforming_strings = offpor sí mismo, algo que un rol ordinario puede hacer.
El código corre en ambos modos, pero la ruta del atacante remoto es el modo servidor, donde otras personas tienen cuentas de pgAdmin. El modo escritorio solo lo expone a quien ya puede operar ese escritorio.
Para la evasión del bloqueo, CVE-2026-116238, todas estas condiciones deben cumplirse:
- Usted ejecuta pgAdmin 4 en modo servidor, en cualquier versión anterior a 9.19. El modo escritorio no está afectado.
- La instancia es alcanzable por cualquiera en quien no confíe plenamente.
- Existe una cuenta interna, que es el estado por defecto: el administrador inicial es una, y
MAX_LOGIN_ATTEMPTSes 3 por defecto.
| Rama | Versiones afectadas | Versión corregida |
|---|---|---|
| pgAdmin 4 | toda publicación anterior a 9.19 | 9.19 (2026-10-29) |
El lote es grande: la autoridad de numeración CVE (CNA) de PostgreSQL publicó catorce registros, dos críticos, diez altos, uno medio y uno bajo, todos corregidos en la misma publicación.
Compruebe qué ejecuta. El contenedor registra su versión al arrancar:
docker logs pgadmin 2>&1 | grep -m1 "Starting pgAdmin"
Para una instalación pip:
pip show pgadmin4 | head -2
En la interfaz, el cuadro Acerca de del menú Ayuda muestra la versión. En la imagen Docker, la etiqueta también la lleva, pero las etiquetas flotantes como latest requieren una de las comprobaciones de versión de esta sección.
¿Cómo lo corrijo?
Actualice cada despliegue a 9.19, la única versión corregida:
- Haga una copia de seguridad de la base de datos de configuración de pgAdmin, que guarda usuarios, roles y definiciones de servidores.
- Antes de actualizar o reiniciar, revise los cuatro cambios del modo servidor y configure las listas de permitidos necesarias. La migración de comandos de contraseña puede borrar permanentemente los comandos que no coincidan.
- Actualice el despliegue. Para pip:
pip install --upgrade pgadmin4==9.19
- En Docker Compose, configure primero la imagen del servicio pgadmin como
dpage/pgadmin4:9.19en el archivo Compose; después descargue la imagen y recree ese servicio:
docker compose pull pgadmin
docker compose up -d pgadmin
El modo servidor en 9.19 bloquea cuatro cosas, cada una con un coste que planificar:
- Los comandos de ejecución de contraseña en texto libre ya no corren.
ENABLE_SERVER_PASS_EXEC_CMDestá obsoleto y se ignora. Los administradores ahora listan los comandos permitidos por nombre enSERVER_PASSEXEC_COMMANDS; en el primer arranque tras actualizar, cada comando existente que coincida exactamente con una entrada listada se convierte en su nombre y cualquier otro comando se borra, sin vuelta atrás. SiSERVER_PASSEXEC_COMMANDSestá vacío, la conversión se pospone y se registra una advertencia en cada arranque. - Los servicios de conexión están restringidos.
SERVER_MODE_ALLOWED_SERVICESestá vacío por defecto, así que los servidores configurados con un servicio de conexión dejan de conectarse hasta que el administrador lo liste. Listar un servicio comparte sus credenciales con todos los usuarios de pgAdmin. - Los túneles SSH ya no leen la identidad SSH propia de la cuenta de servicio. En modo servidor ya no usan sus claves
~/.ssh, su archivo de configuración ni su agente, así que un túnel que dependía de ellos debe recibir un archivo de identidad en el almacenamiento del usuario o una contraseña en el servidor. - En Python 3.10 y posteriores, pgAdmin exige Flask-Security-Too 5.8.3 o posterior, que corrige una redirección abierta, GHSA-ccgp-pv8r-95wm. Esto importa a quien instale el wheel en un entorno existente.
Para los despliegues que aún no pueden actualizar, una solución temporal cierra la ruta de inyección de CVE-2026-116241:
- Retire el permiso Import/Export Data a todo rol al que no confiaría comandos sobre el host. En modo servidor, un administrador retira el permiso
tools_import_export_dataa los roles en la gestión de roles de la interfaz. - La solución cubre solo CVE-2026-116241. No hace nada por la evasión del bloqueo, CVE-2026-116238, ni por las otras doce fallas, y quita la herramienta de exportación a los usuarios que la necesitan.
Para la evasión del bloqueo sin actualizar, restrinja quién puede alcanzar la página de inicio de sesión de pgAdmin: una VPN, una lista de direcciones permitidas en el proxy inverso, o una fuente de autenticación que se interponga por completo. Ninguna cambia las cuatro evasiones; cada una elimina la alcanzabilidad no autenticada que necesitan.
¿Cuál es la causa raíz?
La herramienta Import/Export Data envuelve la consulta SQL de un usuario en un comando psql \copy (...) TO y la valida con una comprobación artesanal de equilibrio de paréntesis cuyas reglas de citas difieren de las de psql. La ruta, POST /import_export/job/<sid>, está protegida solo por el permiso ordinario tools_import_export_data, y pasa la línea \copy renderizada a psql.
La guarda, _is_query_parens_balanced() en web/pgadmin/tools/import_export/__init__.py, recorría la consulta para asegurarse de que nada pudiera cerrar antes de tiempo los paréntesis envolventes. Antes de 9.19, su tratamiento de los identificadores entre comillas dobles era:
# Double-quoted identifier: skip to closing quote
# (handles "" escape sequences — matches strtokx behavior)
if ch == '"':
i += 1
while i < length:
if query[i] == '"':
El comentario afirma coincidir con el tokenizador de psql, strtokx, pero los dos discrepan en la barra invertida. strtokx toma su carácter de escape del ajuste standard_conforming_strings de la conexión y lo aplica a la comilla que abrió el token. Con ese ajuste desactivado, una barra invertida antes de una comilla doble es una comilla escapada para psql, mientras que la guarda de pgAdmin la trata como cierre del identificador. El mensaje del commit de corrección enuncia la consecuencia: en una exportación de consulta como SELECT "a\"b"c) TO PROGRAM 'cmd' x", el paréntesis de cierre ocultado a la comprobación termina el envoltorio \copy (...), y la cláusula TO PROGRAM que sigue corre mediante popen() en el host de pgAdmin antes de que el servidor de base de datos rechace la sentencia.
La evasión del bloqueo, CVE-2026-116238, tiene cuatro huecos separados en un mismo diseño. pgAdmin cuenta los inicios de sesión fallidos y bloquea una cuenta interna tras MAX_LOGIN_ATTEMPTS, pero Flask-Security-Too registra sus propias vistas junto a las de pgAdmin, y las presentaciones a esas vistas omitían por completo el conteo y los controles de pgAdmin: /login iniciaba sesión sin el conteo de fallos, /verify comprobaba la contraseña de un usuario conectado sin límite, /change cambiaba una contraseña sin el permiso de pgAdmin, y /reset aplicaba restablecimientos para cuentas que no son internas o están bloqueadas. Un nombre de usuario escrito en una combinación distinta de mayúsculas y minúsculas no se contaba ni se comprobaba contra el bloqueo. Con la autenticación LDAP o webserver también configurada, una contraseña interna errónea no se contaba nunca.
¿Cómo se explota?
No hay código de prueba de concepto público y no se reporta explotación. Los pasos provienen de los registros CVE y del commit de corrección:
- El atacante necesita una cuenta de pgAdmin con el permiso Import/Export Data, que los no administradores tienen por defecto, y un rol ordinario en un servidor de base de datos destino.
- En esa conexión, el atacante ejecuta
SET standard_conforming_strings = offpara sí mismo, algo que un rol ordinario puede hacer. - Presenta una exportación basada en consulta cuya consulta oculta un paréntesis de cierre dentro de un identificador entre comillas dobles que contiene una barra invertida, seguido de una cláusula
TO PROGRAM. La forma de ejemplo del registro CVE esSELECT "a\"b"c) TO PROGRAM 'cmd' x". - La comprobación de equilibrio de pgAdmin, leyendo la barra invertida de forma distinta a psql, cree que los paréntesis están equilibrados y acepta el trabajo.
- El
\copy (...) TO PROGRAM 'cmd'renderizado corre en psql. Como psql aplica la regla de escape de la conexión, el paréntesis oculto cierra el envoltorio. psql ejecuta entonces el comandoTO PROGRAMmediantepopen()en el host de pgAdmin, como la cuenta del sistema operativo que ejecuta pgAdmin, antes de que el servidor de base de datos rechace la sentencia.
Para la evasión del bloqueo, los pasos son más simples:
- Un atacante no autenticado alcanza la página de inicio de sesión de pgAdmin de un despliegue en modo servidor.
- Presenta intentos de contraseña contra la cuenta administradora interna por una de las rutas no contadas. Esas rutas son la propia vista
/loginde Flask-Security, un nombre de usuario escrito en una combinación distinta de mayúsculas y minúsculas, y una contraseña interna errónea mientras la autenticación LDAP o webserver también está configurada. - Nunca se activa ningún bloqueo, así que los intentos continúan sin límite hasta que la contraseña cae.
¿Cómo fluye el ataque?
Una cuenta ordinaria de pgAdmin convierte una exportación de consulta en un comando del sistema operativo desincronizando la comprobación de citas de pgAdmin respecto al tokenizador de psql. La comprobación que falla es la lectura de la barra invertida por la guarda de paréntesis.
y un rol ordinario"] --> B["SET standard_conforming_strings=off
en la conexión"] B --> C["La consulta de exportación oculta ) en un
identificador con barra + TO PROGRAM"] C --> D["falla la comprobación: la guarda lee la
barra invertida distinto que psql"] D --> E["\\copy (...) TO PROGRAM
corre en psql vía popen"] E --> F["Comando arbitrario ejecutado en
el host de pgAdmin"]
¿Qué puede hacer un atacante?
La inyección, CVE-2026-116241, ejecuta un comando arbitrario del sistema operativo en el host que ejecuta pgAdmin, como la cuenta que ejecuta pgAdmin. En el contenedor oficial esa cuenta suele ser el usuario pgadmin sin privilegios de root (UID 5050); en una instalación pip o de paquete es quien ejecuta el servidor web. El comando corre antes de que el servidor de base de datos rechace la sentencia, así que el ataque no necesita privilegios en la base más allá de un rol ordinario. El CNA de PostgreSQL la puntúa 9.9 Critical, CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H, y 9.4 Critical en CVSS v4.0, CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H.
La evasión del bloqueo, CVE-2026-116238, da a un atacante no autenticado intentos ilimitados de contraseña contra cualquier cuenta interna en la configuración por defecto, incluida la del administrador. El mismo CVE registra que un restablecimiento de contraseña podía enviarse para, o aplicarse a, una cuenta de otra fuente de autenticación cuando las cuentas comparten una dirección de correo. Sus puntuaciones son 8.1 High en CVSS v3.1, CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H, y 9.2 Critical en CVSS v4.0, CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N.
Ninguna de las dos fallas tiene exploit público, reporte de explotación ni entrada en KEV al 31 de octubre de 2026. La inyección es la tercera ronda de una misma familia. CVE-2025-13780 ejecutaba comandos mediante restauraciones de volcados en formato PLAIN hasta 9.10. CVE-2026-17566, corregido en 9.17 en julio de 2026, cerró el hueco de la misma guarda de exportación para las cadenas entre comillas simples. Esta lo reabre mediante identificadores entre comillas dobles.
¿Por qué funciona la corrección?
El commit 9be31a019f78, «Reject a backslash in a double-quoted identifier in the Import/Export Data query export», cambia la guarda para rechazar el caso ambiguo en lugar de adivinar una lectura:
- # Double-quoted identifier: skip to closing quote
- # (handles "" escape sequences — matches strtokx behavior)
+ # Double-quoted identifier: skip to closing quote.
+ # Handles the "" escape sequence to match psql's strtokx.
+ # A bare backslash is rejected outright for the same reason
+ # as in a single-quoted string (see docstring): strtokx
+ # applies its escape character to whichever quote opened the
+ # token, so under standard_conforming_strings=off a \" is an
+ # escaped quote to psql but a closing quote to us.
if ch == '"':
i += 1
while i < length:
- if query[i] == '"':
+ if query[i] == '\\':
+ return False # ambiguous escape: reject
+ elif query[i] == '"':
if i + 1 < length and query[i + 1] == '"':
i += 2 # escaped quote
else:
La corrección también rechaza una barra invertida o un byte nulo en los nombres de esquema, tabla o columna de una exportación o importación. Esos nombres llegan al mismo comando \copy mediante qtIdent(). Las pruebas de regresión del commit fijan ambas direcciones de la desincronización. Cada una lleva una carga TO PROGRAM 'echo pwned' que ahora debe rechazarse: el caso de comilla simple de la corrección de julio, y el caso de comilla doble de esta.
La evasión del bloqueo se corrige con cuatro cambios:
- las presentaciones a las propias vistas de Flask-Security se rechazan con HTTP 405
- cada inicio de sesión interno fallido se cuenta, sea cual sea la mayúscula del nombre de usuario y estén configuradas las fuentes de autenticación que estén
- un inicio de sesión que envía una contraseña ya no recae en una fuente que no la comprueba
- solo se bloquea la autenticación interna, de modo que una cuenta interna bloqueada ya no bloquea a las demás fuentes
La corrección de la inyección es completa para esta guarda. Todo token citado que un comando \copy puede contener, cadenas entre comillas simples e identificadores entre comillas dobles por igual, rechaza ya la barra invertida. Los nombres de parámetros quedan cubiertos por el rechazo de barra invertida y byte nulo en qtIdent(). La corrección del bloqueo cierra las cuatro rutas de evasión que describe el registro CVE.
¿Cómo compruebo si estuve comprometido?
Para la inyección no hay indicadores de compromiso publicados. Revise los registros que su despliegue conserva de los trabajos Import/Export: el panel Procesos de la interfaz de pgAdmin los lista, y su historial se guarda en la base de datos de configuración de pgAdmin. Busque exportaciones cuya consulta contenga TO PROGRAM:
# The Processes panel or the job history in the pgAdmin SQLite database.
sqlite3 -readonly /var/lib/pgadmin/pgadmin4.db \
"SELECT pid, start_time, command, arguments FROM process WHERE arguments LIKE '%TO PROGRAM%';"
Una coincidencia significa que un trabajo intentó ejecutar un programa mediante psql y es una pista, no una prueba, porque una exportación legítima puede mencionar TO PROGRAM en un comentario. Luego compruebe en el host lo que un comando popen() pudo dejar: procesos desconocidos, entradas de cron y archivos bajo el directorio personal del usuario de pgAdmin.
Un SET standard_conforming_strings de sesión no persiste como configuración, pero puede quedar en los registros de sentencias o auditoría. Revise los registros conservados; la ausencia de historial de trabajos no descarta un intento.
Para la evasión del bloqueo, busque en los registros de pgAdmin un volumen de inicios de sesión fallidos que nunca activó un bloqueo, que es la firma propia de la evasión. No se ha reportado ninguna víctima, indicador ni campaña en ningún lugar al 31 de octubre de 2026.
Cronología
- 2026-07-27: pgAdmin integra 1496fabe, la corrección de CVE-2026-17566, que cierra el hueco de la barra invertida en cadenas entre comillas simples de la misma guarda.
- 2026-07-31: pgAdmin 9.17 entrega esa corrección, y el registro CVE-2026-17566 se publica el mismo día.
- 2026-10-23: pgAdmin integra 9be31a019f78, la corrección del identificador entre comillas dobles para CVE-2026-116241, escrito por hiteshjambhale con Dave Page, reportado por Yonghwa Lee de Xint by Theori.
- 2026-10-25: pgAdmin integra las correcciones de la evasión del bloqueo para CVE-2026-116238, incluida 58c531d83117.
- 2026-10-29: pgAdmin 4 9.19 se publica, subido a PyPI a las 15:36 UTC, con notas de publicación que cubren las 14 correcciones de seguridad.
- 2026-10-30: el CNA de PostgreSQL publica el lote de 14 registros CVE a las 19:55 UTC.
Preguntas frecuentes
¿Me protege 9.18?
No. 9.18 lleva la corrección de julio para CVE-2026-17566, que cerró el caso de la barra invertida en cadenas entre comillas simples, pero el caso del identificador entre comillas dobles de CVE-2026-116241 vence esa corrección. Las evasiones del bloqueo de CVE-2026-116238 también están presentes en 9.18. Solo 9.19 corrige ambas.
¿Está afectado el modo escritorio?
El código de la inyección corre en ambos modos, pero explotarlo de forma remota exige el modo servidor, donde otras personas tienen cuentas y pueden alcanzar la herramienta. El modo escritorio solo expone la misma falla a quien ya puede operar ese escritorio. La evasión del bloqueo es solo del modo servidor; el modo escritorio no está afectado.
¿Qué se rompe al actualizar un despliegue en modo servidor a 9.19?
El modo servidor cambia cuatro cosas, todas listadas en «¿Cómo lo corrijo?». Las dos más costosas de planificar son la lista de comandos de contraseña, que convierte o borra los comandos existentes en el primer arranque, y SERVER_MODE_ALLOWED_SERVICES, que todo servidor configurado con un servicio de conexión necesita antes de volver a conectarse.
¿Las otras doce CVE también se corrigen en 9.19?
Sí. Los 14 registros dicen «before 9.19», y las notas de publicación de 9.19 llevan una línea de corrección para cada uno. Van desde las carreras de rutas del gestor de archivos de CVE-2026-116240 hasta los usos de credenciales de la cuenta de servicio de CVE-2026-116242 a CVE-2026-116247. Una sola actualización corrige el lote.
¿Quién encontró estas fallas?
Yonghwa Lee de Xint by Theori reportó la inyección, CVE-2026-116241. Yves Soete de Blacksight LLC reportó la confusión de inicio de sesión entre fuentes de CVE-2026-116239, y Harsh Singh la evasión de ruta de binarios de CVE-2026-116243. La evasión del bloqueo, CVE-2026-116238, no cita reportador externo en el registro CVE, que acredita a Ashesh Vashi de EnterpriseDB el desarrollo de la corrección.
Inventarie sus servidores pgAdmin expuestos con Ostorlab
El descubrimiento de superficie de ataque de Ostorlab inventaría sus servidores y servicios expuestos en Internet. Muestra qué despliegues ejecutan un punto de acceso pgAdmin expuesto a la red, para saber cuáles mover a 9.19 primero y cuáles reubicar tras una VPN.
Referencias
- Registro CVE-2026-116241, publicado por el CNA de PostgreSQL con CVSS v3.1 9.9 y CVSS v4.0 9.4, acreditando a Yonghwa Lee: https://www.cve.org/CVERecord?id=CVE-2026-116241 (consultado el 31 de octubre de 2026)
- Registro CVE-2026-116238, las cuatro evasiones del bloqueo, con CVSS v3.1 8.1 y CVSS v4.0 9.2: https://www.cve.org/CVERecord?id=CVE-2026-116238 (consultado el 31 de octubre de 2026)
- Notas de publicación de pgAdmin 4 9.19, con fecha 29 de octubre de 2026, con las líneas de corrección de seguridad para los tickets #10663, #10676, #10674 y el resto del lote, y los cuatro cambios del modo servidor: https://www.pgadmin.org/docs/pgadmin4/9.19/release_notes_9_19.html (consultado el 31 de octubre de 2026)
- Commit de corrección 9be31a019f78 para CVE-2026-116241, cuyo mensaje documenta la desincronización de
strtokxy la consulta de ejemplo, reportado por Yonghwa Lee de Xint by Theori: https://github.com/pgadmin-org/pgadmin4/commit/9be31a019f8780647fb689c458408c8803263c812 (consultado el 31 de octubre de 2026) - Commit de corrección 58c531d83117 para CVE-2026-116238, «Act only on the internal account when accounts from other sources share its email or username»: https://github.com/pgadmin-org/pgadmin4/commit/58c531d831179dc9b0a5bf873ccb80c71c8d387a (consultado el 31 de octubre de 2026)
- Registro CVE-2026-17566, la corrección de julio del caso de comilla simple en la misma guarda, con el commit 1496fabe: https://www.cve.org/CVERecord?id=CVE-2026-17566 (consultado el 31 de octubre de 2026)
- Registro CVE-2025-13780, primera ronda de la familia, ejecución de comandos mediante restauraciones de volcados en formato PLAIN hasta 9.10: https://www.cve.org/CVERecord?id=CVE-2025-13780 (consultado el 31 de octubre de 2026)
- pgAdmin 4 9.19 en PyPI, subido el 29 de octubre de 2026 a las 15:36 UTC: https://pypi.org/project/pgadmin4/ (consultado el 31 de octubre de 2026)
- Imagen Docker dpage/pgadmin4, canal de despliegue estándar del modo servidor, con 453,158,170 descargas: https://hub.docker.com/r/dpage/pgadmin4 (consultado el 31 de octubre de 2026)
-
Catálogo Known Exploited Vulnerabilities de CISA, fuente JSON, buscado para ambos críticos: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (consultado el 31 de octubre de 2026)
-
Documentación oficial del contenedor, usuario sin privilegios de root y configuración: https://www.pgadmin.org/docs/pgadmin4/9.19/container_deployment.html