Volver al Blog

«La columna estaba cifrada»: pgcrypto guardaba texto plano y nadie se enteraba

«La columna estaba cifrada»: pgcrypto guardaba texto plano y nadie se enteraba

El 13 de agosto PostgreSQL publicó 18.6, 17.11, 16.15, 15.19 y 14.24 y cerró 28 vulnerabilidades y más de 110 fallos de una tacada. Doce de las veintiocho son desbordamientos de memoria, y otras cinco confusiones de tipo, con títulos casi intercambiables. Una no se parece a ninguna. La CVE-2026-14663 dice que, en ciertas condiciones, pgcrypto escribía texto plano en la columna que tú creías cifrada. Sin error, sin aviso, sin nada en el log.

Las notas de la versión 18.6 lo cuentan sin anestesia: «Previously, if OpenSSL rejected the requested cipher (for example, because it is running in FIPS mode, or the legacy provider hasn't been loaded), pgcrypto failed to notice the failure and simply XOR'd the non-encrypted block with the plaintext, rendering the "encryption" trivially breakable». Es decir: pgcrypto le pedía a OpenSSL un algoritmo, OpenSSL respondía que no, pgcrypto no leía la respuesta y seguía adelante. Lo que acababa en el bytea no era texto cifrado: era el dato con una capa de XOR sobre un bloque que nunca llegó a cifrarse.

La ficha del CVE cierra el círculo con una frase que no admite lectura amable: el fallo «allows a user to recover cleartext, via direct observation of the faulty ciphertext». Mirando el dato guardado. Y hay una segunda parte, por si la primera se quedaba corta: «If the application accepts encrypted data as input, decryption will succeed even with the wrong key». La clave había dejado de importar. Las funciones afectadas son las ocho de PGP: pgp_sym_encrypt, pgp_pub_encrypt, sus parejas de descifrado y las cuatro variantes _bytea.

Antes de que cunda el pánico: por defecto, bien

Esto no le pasa a todo el mundo, y conviene decirlo antes que nada. El algoritmo por defecto de pgp_sym_encrypt es aes128, y AES está disponible en cualquier OpenSSL moderno. Los algoritmos que disparan el fallo son los viejos: las notas de versión citan blowfish/bf, twofish, cast5 y 3des. PostgreSQL le pone un 6,5 de CVSS, y el número tiene su lógica: hace falta que se den las dos condiciones a la vez. Si tu aplicación nunca ha escrito la opción cipher-algo, casi seguro que esto no va contigo. Casi: esas opciones sólo mandan sobre lo que cifras tú. «Decrypt functions get the parameters from the PGP data», dice la documentación. Si tu aplicación descifra mensajes PGP que llegan de fuera, el algoritmo lo eligió quien los creó, y hasta ahí no llega ningún grep de tu repositorio.

Lo incómodo llega después. Nadie escribe cipher-algo=bf en 2026. Se escribió hace más de una década, copiado de un ejemplo que entonces era razonable, y sigue ahí porque la columna se leía bien y nadie tuvo motivo para volver a mirarla. Los sitios donde aparece esa cadena suelen ser los mismos: integraciones con GnuPG antiguo, importadores de ficheros de otro proveedor, procedimientos almacenados que sobrevivieron a tres migraciones.

El día que se rompió no fue el día que se escribió

El código que pide Blowfish puede haber estado cifrando correctamente durante años. Lo que cambia no es la aplicación: es la máquina de debajo. La documentación de pgcrypto lo avisa en una línea: «When compiled against OpenSSL 3.0.0 and later versions, the legacy provider must be activated in the openssl.cnf configuration file in order to use older ciphers like DES or Blowfish». OpenSSL 3 mandó Blowfish y CAST5 al proveedor legacy, que no se carga solo; 3DES se queda en el proveedor por defecto y desaparece por otra puerta, la del modo FIPS. Dos caminos distintos hacia el mismo sitio.

Junta las dos piezas y sale una escena que hemos visto muchas veces con otros nombres: alguien migra el servidor de base de datos a una distribución más nueva un sábado por la mañana. La aplicación no se toca. Los tests pasan, porque los tests cifran y descifran con el mismo servidor —cifran mal y descifran mal, de forma perfectamente coherente—. Nada se cae, nada aparece en la monitorización, nadie abre un ticket. Y desde ese sábado, lo que entra en esa columna se puede leer. Ya escribimos aquí sobre un control que fallaba abriendo; este ni siquiera falla: devuelve OK.

La buena noticia: a partir de ahora se rompe

El arreglo, firmado por Daniel Gustafsson, hace lo único sensato: convertir el silencio en ruido. «By default, pgcrypto will now fail to decrypt any messages that were affected in this way». Traducido: actualizas el servidor y, si tenías el problema, la aplicación deja de leer esa columna. Un lunes por la mañana, con un ticket. Ese ticket es el detector más barato que vas a tener este mes, y viene dentro del paquete: si después de actualizar nada deja de descifrarse, tampoco tenías el fallo. Si algo deja de descifrarse, ya tienes por dónde empezar a mirar.

La salida de emergencia y su letra pequeña

Para recuperar los datos que quedaron así, PostgreSQL ha añadido una opción nueva a pgp_sym_decrypt y pgp_pub_decrypt: ignore-cipher-failure=1, que restaura el comportamiento anterior y permite quitar el envoltorio defectuoso para volver a cifrar con un algoritmo moderno. La documentación la abre con la palabra «Dangerous!» y añade la frase que hay que leer despacio: «Such faulty messages do not require the correct decryption key when ignore-cipher-failure is enabled, so there is no guarantee that the decrypted plaintext actually originated from a holder of the key». Para sacar esos datos no hace falta la clave. Que es otra manera de decir lo mismo de antes: no estaban cifrados.

Y hay una restricción de orden que se pasa por alto con facilidad: «It is important however that the behavior of OpenSSL be the same as it was when the faulty messages were created: if the set of unsupported algorithms is not the same, this approach will not work». El rescate hay que hacerlo donde se estropeó, con el mismo OpenSSL debajo. Si el plan era «actualizamos el servidor, lo movemos a la máquina nueva y luego, con calma, miramos lo de la columna», el plan está del revés. Primero se rescata y se vuelve a cifrar con aes256; después se muda.

Y conviene no mezclar dos casos que desde fuera se parecen mucho. Uno es el mensaje que se cifró mal, y es el que arregla esta opción. El otro es el mensaje que se cifró bien, con un algoritmo que hoy tu OpenSSL no ofrece; ahí la documentación es tajante: ignore-cipher-failure «is not necessary or helpful for that scenario», y lo que toca es habilitar el proveedor que falta. Si aun así se usa la opción y el mensaje bien cifrado pasa las comprobaciones de integridad de PGP, «that result is coincidental and does not make the recovered plaintext trustworthy». Puede devolverte algo con buena pinta que no lo sea.

Qué versión tienes de verdad

Aquí hay dos trampas, y las dos afectan a cómo compruebas si estás al día. La primera está en la propia ficha oficial del CVE, que dice: «Versions before PostgreSQL 18.5, 17.11, 16.15, 15.19, and 14.24 are affected», y lista 18.5 como versión corregida. 18.5 no existe. El anuncio del mismo día lo explica: «This release skips PostgreSQL 18 versions from PostgreSQL 18.4 to 18.6. 18.5 was not shipped due to a regression». Si tu inventario busca el número que pone la ficha, no lo encontrará nunca. Para la rama 18 el número bueno es 18.6.

La segunda trampa es de dónde vienen tus paquetes. Lo hemos mirado hoy, 20 de agosto por la tarde, contra los repositorios públicos, y no da lo mismo:

  • PGDG (apt.postgresql.org), para bookworm y trixie: ya sirve las versiones corregidas de las cinco ramas — 14.24, 15.19, 16.15, 17.11 y 18.6 (en trixie, 17.11-1.pgdg13+2).
  • Debian 13 (trixie), repositorio principal: postgresql-17 sigue en 17.10-0+deb13u1, que el rastreador de seguridad de Debian marca como vulnerable. El arreglo va por el canal de seguridad: 17.11-0+deb13u1, publicado como DSA-6438-1. Si esa máquina no tiene la línea trixie-security en su sources.list, hoy sigue con el fallo.
  • Debian 12 (bookworm): lo mismo con postgresql-1515.18 en el principal, 15.19-0+deb12u1 en seguridad, como DLA-4740-1.
  • Si sigues en PostgreSQL 13 (bullseye), el rastreador lo da por vulnerable y sin versión corregida. Ahí no hay parche que aplicar: hay una migración que planificar. Y para quien esté en la rama 14, conviene apuntarlo ya: PostgreSQL 14 deja de recibir arreglos el 12 de noviembre de 2026.

Y el papel que dice que estaba cifrado

Si en el registro de actividades de tratamiento, en la política de seguridad o en la última respuesta a un cuestionario de cliente hay una línea que dice «los datos sensibles se almacenan cifrados en la base de datos», esa línea pudo dejar de ser cierta el día que esa máquina cambió de OpenSSL. El artículo 34.3.a) del RGPD permite ahorrarse la comunicación a los afectados cuando las medidas aplicadas hacían los datos ininteligibles para quien no está autorizado, y pone el cifrado como ejemplo. Un XOR que se deshace sin clave difícilmente encaja ahí.

No somos abogados y esa valoración no nos toca. Lo que sí es nuestro es lo de debajo: quien tenga que decidir necesita saber qué algoritmo pedía la aplicación, qué OpenSSL había en esa máquina y desde qué fecha. Las tres cosas se reconstruyen —el código, el openssl.cnf, la fecha del cambio de sistema operativo—, y salen mucho mejor reconstruidas hoy, con calma, que dentro de un expediente con plazo de setenta y dos horas.

Qué haríamos esta semana

  • Buscar la cadena, no el CVE. grep -ri "cipher-algo" en el código, en los procedimientos almacenados y en los jobs de importación. Si no aparece, el asunto está cerrado y has tardado diez minutos.
  • Mirar el servidor, no la aplicación. SELECT version(); contrastado con 18.6 / 17.11 / 16.15 / 15.19 / 14.24, y un vistazo a si el openssl.cnf de esa máquina carga el proveedor legacy. Las dos respuestas juntas dicen si el fallo era posible ahí.
  • Dejar que el parche haga de auditor. Actualizar primero una copia de la base y leer las columnas afectadas. Lo que deje de descifrarse es la lista de trabajo, con la salvedad de antes: puede ser un dato mal cifrado o uno bien cifrado con un algoritmo que ya no está cargado, y no se tratan igual.
  • Rescatar antes de mudar. Si hay que usar ignore-cipher-failure, en la máquina de origen y con su OpenSSL. Volver a cifrar con aes256, y anotar quién, cuándo y sobre qué filas: eso es lo que se pedirá después.
  • Y de paso, preguntar por la clave. pgcrypto trabaja dentro del servidor, y su documentación lo dice sin rodeos: «all the data and passwords move between pgcrypto and client applications in clear text», y por eso hay que «[t]rust both system and database administrator». Si esa columna guarda lo que sospechamos, el debate no era sólo el algoritmo. Del mismo cajón: el panel de datos que también era el llavero, y el fichero que decía ser un PNG.

Lo raro de esta vulnerabilidad es que no necesita que nadie te ataque para hacerte daño. No hay explotación en curso, no hay campaña, no hay actor con nombre de animal. Hay una columna que puede llevar años guardando en claro exactamente lo que se puso ahí para que no se leyera, y una sola persona en el mundo puede descubrirlo: la que se siente a mirarla. La versión nueva ayuda de la única forma en que un programa puede ayudar en esto — por fin protesta.

Fuentes (verificadas el 20 de agosto de 2026): la fecha, las versiones publicadas, las 28 vulnerabilidades y los más de 110 fallos, la frase sobre la 18.5 no publicada y el fin de soporte de PostgreSQL 14, del anuncio oficial del 13 de agosto de 2026. La descripción del fallo, las funciones afectadas, la frase sobre la clave incorrecta y las versiones «before 18.5, 17.11, 16.15, 15.19, and 14.24», de la ficha de CVE-2026-14663. La explicación del XOR, la lista de algoritmos que lo disparan, la autoría de Daniel Gustafsson y el comportamiento nuevo al descifrar, de las notas de la versión 18.6. El algoritmo por defecto, la advertencia sobre el proveedor legacy de OpenSSL 3, el aviso «Dangerous!» de ignore-cipher-failure, la frase sobre la clave no necesaria, la condición de que OpenSSL se comporte igual y las limitaciones de seguridad citadas, de la documentación de pgcrypto. El estado por distribución, el DSA-6438-1 y el DLA-4740-1, del rastreador de seguridad de Debian. Son nuestras las consultas de hoy a los índices públicos de apt.postgresql.org (ramas trixie-pgdg y bookworm-pgdg); son reproducibles y dan la misma respuesta desde cualquier sitio. Es nuestra también la lectura sobre el escenario de la migración de sistema operativo, declarada como lectura en el texto. Las citas se dejan en inglés, su idioma original, para que se puedan verificar palabra por palabra.

¿Sabes qué algoritmo pide tu aplicación?

Mantenemos bases de datos y aplicaciones de negocio que llevan años en producción, con la parte aburrida escrita: qué versión corre, de qué repositorio viene y qué se apoya en qué. Si esta semana toca abrir la caja de una columna cifrada que nadie ha mirado desde hace tiempo, es exactamente el trabajo que hacemos en datos y aplicaciones, y la parte de decidir qué se documenta y qué se notifica, en cumplimiento y continuidad.

Hablar con everyWAN

Etiquetas:

Compartir:

Suscríbete a nuestra newsletter

Para recibir historias del mundo IT, novedades de everyWAN y ofertas exclusivas para suscriptores, date de alta a nuestra lista de correo

everyWAN
everyWAN