Certificados de eMudhra: qué necesita un atacante y qué controles lo frenan
Quien tenga las claves de eMudhra puede forjar certificados, pero cada ataque exige otras condiciones: cinco vectores y cómo cortarlos.
Este artículo es educativo y no sustituye una auditoría formal de smart contracts, asesoría legal, asesoría financiera ni una revisión de seguridad específica del protocolo.
Las claves privadas de ocho CA intermedias de eMudhra, seis de TLS y dos de firma de código, se pueden recuperar a partir de los propios certificados. Con las seis de TLS, cualquiera puede firmar un certificado para cualquier dominio, y un cliente que no se ha actualizado lo aceptará como válido. Con las dos de firma de código se podía firmar software que Windows aceptaba como firmado. Para interceptar tráfico eso no basta: además hay que estar en medio de la conexión.
Una CA o una intermedia comprometida, por sí sola, no da interceptación ni ejecución remota de código. Para interceptar, alguien tiene que controlar además la ruta de red, el DNS, un proxy o el punto desde el que se descargan los paquetes, y el cliente tiene que seguir confiando en la cadena afectada. Actualizar las raíces, o bloquear las intermedias, quita esa confianza. Los hashes, las firmas y las comprobaciones de rango frenan la manipulación de paquetes y datos, pero no la lectura de tokens o tráfico.

Las condiciones de un ataque
Para interceptar o manipular una conexión con estas claves tienen que darse dos cosas a la vez:
- El cliente sigue confiando en la cadena afectada y no exige Certificate Transparency (CT).
- El atacante controla un punto del camino entre el cliente y el servidor: la red, la resolución DNS, un proxy o el punto de entrega de paquetes.
Con eso basta para leer un token o una petición. Para colar un paquete o un dato alterado hace falta además una tercera condición: que ningún otro control detecte el cambio, como un hash fijado, una firma sobre los datos o una comprobación de rango.
La primera condición tiene un matiz. Las seis intermedias de TLS cuelgan de emSign Root TLS CA - G3 y las dos de firma de código, de emSign Root CS CA - G3; las dos están bajo emSign ECC Root CA - G3, que es la raíz que aparece en los almacenes de confianza. Además, cada intermedia tiene una firma cruzada de AAA Certificate Services, una raíz de Sectigo. Quitar solo emSign ECC Root CA - G3 no basta si el almacén sigue aceptando AAA para TLS, como pasa en el cacerts de OpenJDK sin corregir o en versiones antiguas de certifi. Lo que corta los dos caminos es actualizar, porque las versiones corregidas bloquean las intermedias.
Cloudflare lo describe así: como las claves son débiles, un atacante puede crear un certificado falso para cualquier dominio y usarlo para suplantar servidores e interceptar o modificar tráfico. Es la mecánica de cualquier ataque de intermediario. La diferencia es que el certificado falso pasa la verificación del cliente que no se ha actualizado.
Google y Dennis Jackson, de Mozilla, coinciden en que no hay certificados de estas intermedias en los registros de CT, así que los navegadores que exigen CT no pudieron ser atacados. El riesgo está en los clientes que no exigen CT: curl, los runtimes de lenguajes, pip, uv y las imágenes de contenedor. Docker lo dice de su CLI y su daemon en las notas de Docker Desktop 4.96.3, del 22 de octubre: un atacante con acceso a la red podría haber usado estos certificados para interceptar sus conexiones TLS.
Según la propia CA, las intermedias se crearon el 30 de abril de 2025, siete de las ocho nunca se usaron en producción y la que sí se usó no emitió ningún certificado. Jackson escribe que no se sabe si alguien aprovechó el fallo contra clientes que no exigen CT. No hay pruebas de abuso publicadas, pero tampoco una forma de descartarlo desde fuera. Por eso tiene sentido revisar qué tráfico pasó por esos clientes.

Vector A: suplantar una API externa
Con estas claves se pueden forjar certificados TLS válidos para cualquier dominio, pero solo sirven para interceptar si el atacante controla además la ruta de red (Wi-Fi, proxy, DNS o BGP) y el cliente todavía confía en la cadena y no exige Certificate Transparency. Chrome, Firefox, Edge y Safari ya bloquean las intermedias y exigen CT.
Un backend, un job de CI o un agente llama por HTTPS a una API externa con un token en la cabecera. Si el atacante controla un punto de intercepción y el cliente acepta la cadena falsa, la llamada llega al atacante en lugar de al proveedor. Puede leer el token, las peticiones y las respuestas, y también cambiarlas.
Aquí ningún hash ayuda: si el token viaja por la conexión interceptada, el atacante lo lee. Lo que corta este vector son las raíces al día o las intermedias bloqueadas en el cliente, el pinning o mTLS.
Vector B: artefactos descargados por HTTPS
Si el atacante consigue interceptar una descarga HTTPS (docker pull por tag, pip, npm o curl | sh), puede servir un artefacto manipulado, pero solo cuando nada verifica su integridad por otra vía. Un digest de imagen fijado, el integrity del lockfile de npm, pip --require-hashes, la verificación con cosign/Sigstore o los repositorios apt/rpm firmados con GPG detectan el cambio.
Durante un build o un despliegue se descargan imágenes, paquetes y scripts. Si el atacante controla el punto de entrega y el cliente acepta la cadena falsa, el artefacto sustituido se ejecuta en el build o en producción y puede leer los secretos que tenga a mano. Cada forma de descarga tiene su defensa, o no la tiene:
- «docker pull imagen:tag» resuelve el tag por el canal interceptado. «imagen@sha256:…» se direcciona por contenido.
- npm comprueba el integrity del lockfile para lo que ya está fijado en él, pero no protege la primera resolución ni un «npm install» de un paquete nuevo.
- pip y uv instalan lo que les sirvan salvo que se exijan hashes.
- «curl | sh» no tiene ninguna defensa: lo que llega se ejecuta.
- apt y rpm verifican metadatos firmados con GPG con una clave que ya está en la máquina, aunque la descarga vaya sin TLS.
En Python hay un detalle que cambia el orden de las cosas. Christian Heimes explica en el hilo de Python Discourse sobre el incidente que pip carga siempre las raíces de su certifi interno, además de las del sistema, y que no hay forma soportada de desactivarlo. Actualizar el sistema no basta: hay que actualizar pip.
Vector C: datos que alimentan decisiones
Un servicio consulta por HTTPS precios, saldos o parámetros de riesgo y decide con ellos de forma automática. Si un atacante en la ruta suplanta al proveedor con un certificado falso, puede devolver datos alterados. El cliente los recibe por un canal que parece válido, y el error acaba en decisiones de colateral, liquidaciones, valoraciones o cierre de operaciones.
Aquí TLS es solo una capa. Los controles que funcionan aunque el canal falle están en los propios datos:
- Fuentes firmadas: el proveedor firma los datos y el cliente verifica la firma con una clave que conoce de antemano, con independencia del certificado del servidor.
- Fuentes independientes: comparar el mismo dato de más de un proveedor u oráculo y no actuar si discrepan.
- Límites: rechazar valores fuera del rango esperado o con cambios bruscos frente al último valor aceptado.
- Frescura: exigir una marca de tiempo y descartar datos antiguos o repetidos.
- Raíces actualizadas en el cliente, igual que en los vectores A y B.
Cuando alguna de esas comprobaciones falla, lo prudente es detener la automatización y pasar la decisión a una persona en lugar de actuar con el dato dudoso.
Vector D: firma de código en Windows
Dos de las ocho intermedias (EM OV CS CA - G3B y G3C) son CA de firma de código (EKU codeSigning) y encadenan a raíces en las que Windows confía para Code Signing (emSign Root CS CA - G3 y AAA Certificate Services): con sus claves se podía firmar software que Windows aceptaba como firmado de forma válida, hasta que Microsoft las bloqueó el 20-10-2026.
El informe de incidente de Mozilla (Bugzilla 2083449) lo dice así: seis de los certificados son de TLS y los otros dos son de firma de código y Microsoft confía en ellos. Este vector no necesita estar en la ruta de red, pero tampoco da ejecución remota: alguien tiene que descargar y ejecutar el binario firmado. No hay constancia pública de binarios firmados con estas intermedias.
Vector E: cargas firmadas y certificados de cliente

Con las claves de las seis intermedias TLS, un atacante puede emitirse un certificado nuevo con el nombre que quiera, firmar con él un payload alterado o presentarlo como certificado de cliente en mTLS. Solo lo aceptaría un backend que dé por buena cualquier firma o certificado de cliente que encadene al almacén público de raíces (certifi, el del sistema o el cacerts de Java) sin fijar la clave, el certificado o la CA que espera. Los sistemas de firma de payloads más habituales (HMAC, una clave pública fijada, JWKS por «kid» o una CA privada) no se ven afectados.
El atacante no obtiene la clave ni el certificado de ningún firmante legítimo, y las firmas que ya existían siguen valiendo lo mismo. Lo que consigue es un certificado propio que pasa por válido ante un verificador que solo comprueba que la cadena llega a una raíz pública. Comprobar el nombre del certificado tampoco protege, porque el atacante pone el que quiera.
Los casos en los que eso puede pasar son concretos:
- Tokens JWS o JWT que traen su propio certificado en la cabecera x5c y se validan contra el almacén público. La RFC 7515 obliga a validar la cadena, pero eso no dice quién firmó: la clave tiene que venir de una fuente que autentique su origen.
- Firmas XML (SAML, WS-Security) verificadas con signxml sin parámetros. Su documentación advierte de que, en ese caso, confía en cualquier firma hecha con un certificado que acepte el almacén de CA del sistema.
- Servidores mTLS que aceptan certificados de cliente de cualquier CA pública y autorizan por el nombre. Las seis intermedias TLS permiten emitir certificados de cliente.
Python no confía en estos certificados por defecto para verificar firmas: la biblioteca estándar no verifica firmas de payloads, y en cryptography hay que construir de forma explícita el almacén y el verificador. Además, el payload alterado tiene que llegar al backend: o el endpoint acepta envíos directos de cualquier firmante válido, o el atacante está en la ruta, como en el vector A.
Lo que no dan: ejecución remota de código
No dan ejecución remota de código directa: el certificado forjado solo permite suplantar un servidor TLS, y el código del atacante solo llega a ejecutarse si la víctima instala y ejecuta un artefacto manipulado (vector B) o si el cliente tiene otra vulnerabilidad aparte.
Ni Mozilla, ni Google, ni Cloudflare describen ejecución de código en sus avisos: hablan de suplantar servidores e interceptar o modificar tráfico.
Cómo protegerte
Los pasos detallados para cada herramienta en macOS están en el artículo de remediación enlazado al final. Aquí va lo mínimo para cada vector, con comandos que se han ejecutado tal cual en Linux.
Frente al vector A: los almacenes de confianza
El primer paso es saber qué versiones tienes. En una máquina o una imagen Debian o Ubuntu:
node --version
python3 -m pip list 2>/dev/null | grep -iE '^(pip|certifi|uv) '
dpkg -s ca-certificates | grep '^Version'Las versiones mínimas que dan las fuentes primarias:
- Node: 24.22.1, 22.23.4 o 26.12.1, según la rama (aviso de seguridad de Node del 21 de octubre).
- certifi 2026.10.19, pip 26.2.2 y uv 0.13.4 (aviso de Seth Larson en Python Discourse).
- ca-certificates: la versión que publique tu distribución con el cambio. Mientras no exista, la página de Jackson da un procedimiento para desconfiar de las raíces afectadas y advierte de que no es para usuarios normales.
- Docker Desktop 4.96.3 o posterior. Sus notas de versión dicen que quita la confianza en estos certificados.
Además, este grep localiza en un repositorio los patrones habituales que desactivan la verificación en Python, Node, Go y curl. Desactivarla nunca es la salida a un error de certificado: un cliente sin verificación acepta cualquier certificado, no solo los de estas intermedias.
grep -rnE --exclude-dir={.git,node_modules,.venv} \
'verify *= *False|CERT_NONE|rejectUnauthorized: *false|NODE_TLS_REJECT_UNAUTHORIZED|InsecureSkipVerify: *true|curl +(-k|--insecure)' .Cada coincidencia hay que leerla: algunas estarán en tests o en herramientas locales. Las que estén en código que llama a APIs externas son las que importan.
Dos medidas más reducen lo que se puede perder. Restringir la salida de red de cada servicio a los destinos que necesita deja menos conexiones que interceptar. Y rotar los tokens que hayan viajado desde clientes sin actualizar por redes que no controlas cierra la ventana hacia atrás.
Java, el caso más complejo
Un JDK sin actualizar no bloquea los certificados de eMudhra: confía en esas intermedias a través de emSign ECC Root CA - G3 y de la firma cruzada de AAA Certificate Services. Y es fácil creer que todo está al día sin que lo esté. El aviso de OpenJDK del 21 de octubre (CVE-2026-91403) explica por qué:
- Un runtime de OpenJDK instalado por separado, descargado como archivo, trae su propio almacén y probablemente contiene los certificados comprometidos. Hay que actualizarlo.
- Los paquetes de OpenJDK de Arch Linux, Debian, Devuan, Fedora, Gentoo, Oracle Linux, Red Hat Enterprise Linux y Ubuntu usan los certificados del sistema operativo; ahí basta con actualizar los del sistema. Los de FreeBSD y Homebrew hay que actualizarlos directamente.
- Si hay más de un runtime instalado, hay que repetir el paso en todos.
- Las imágenes de contenedor con Java llevan su propio JRE y su propio almacén. Actualizar el host no las cambia: hay que reconstruirlas con un JDK corregido.
La corrección se llama JDK-8395142 en OpenJDK y JDK-8395555 en Oracle, y no quita raíces: añade a la lista de certificados bloqueados del JDK las ocho intermedias y sus ocho firmas cruzadas de AAA. Esa lista es el fichero blocked.certs de lib/security, o blacklisted.certs en Java 8. emSign ECC Root CA - G3 y AAA Certificate Services siguen en el cacerts de OpenJDK. Por eso quitar solo la primera no basta, y quitar AAA no se recomienda: puede romper conexiones a servicios que todavía encadenan a esa raíz, y Jackson advierte de que desconfiar de ella no es para usuarios normales.
Los JDK anteriores al 21 de octubre no bloquean estas intermedias. Actualiza a una versión corregida y, si no puedes, sustituye lib/security/blocked.certs por el que publica Oracle en https://ops.java/emudhra (en Java 8, blacklisted.certs) y reinicia las aplicaciones Java.
Versiones que ya traen el bloqueo, según sus notas de versión o comprobado en el binario:
- Oracle JDK 27.0.1.0.2, 25.0.5.0.2, 21.0.13.0.2, 17.0.21.0.2, 11.0.33.0.2 y 8u511 b32 (1.8.0_511-b32), publicadas el 21 de octubre. OpenJDK publica la 27.0.1.0.2 en jdk.java.net.
- Amazon Corretto 25.0.5.0.9, 21.0.13.0.10 y 8.512.08.1.
- Las fórmulas actuales de Homebrew openjdk, openjdk@21 y openjdk@17.
Sin corrección a 23 de octubre: Homebrew openjdk@11 y openjdk@8, Azul Zulu, los paquetes de OpenJDK de Red Hat Enterprise Linux y Temurin 8 y 11. En Oracle JDK 8, la 8u511 publicada el 20 de octubre (1.8.0_511-b09) sigue afectada: si «java -version» no muestra «b32», actualiza. Para Java 8 de Debian y Ubuntu no hay una versión corregida verificada. Temurin 21 y 25 no llevan el bloqueo, pero tampoco traen emSign ECC Root CA - G3 ni AAA Certificate Services, así que rechazan la cadena.
ops.java es el dominio oficial de Oracle para este aviso, pero el 23 de octubre devolvía un error 403, así que no conviene depender solo de él: actualizar sigue siendo la vía principal. El artículo de remediación en macOS enlazado al final trae los pasos detallados: la descarga, un script que comprueba el formato del fichero, conserva las entradas que ya tenía y guarda una copia, y la validación con jshell. Borrar raíces con keytool -delete no es la vía principal.
Para saber dónde mirar, este comando lista los almacenes de los runtimes instalados por separado en una máquina Linux o en un contenedor:
find / -type f -name cacerts -path '*/lib/security/*' 2>/dev/nullPara cada uno, este otro dice si su cacerts contiene alguna de las dos raíces. Busca por huella SHA-256, tomada de la lista que publica Jackson, porque el alias cambia según quién haya construido el JDK. Ajusta JAVA_HOME a la carpeta del JDK que estás revisando:
"$JAVA_HOME/bin/keytool" -list -v -cacerts -storepass changeit \
| awk '/^Alias name:/ {alias=$0}
/SHA256: 86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B/ {print alias, "-> emSign ECC Root CA - G3"}
/SHA256: D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4/ {print alias, "-> AAA Certificate Services"}'Y este cuenta cuántas de las 16 huellas, tomadas de la misma lista, hay en blocked.certs. La primera línea del fichero tiene que ser «Algorithm=SHA-256», y el recuento, 16; un número menor significa que el bloqueo no está o está incompleto. Sirve también para revisar un blocked.certs descargado o copiado antes de instalarlo. En Java 8 la ruta es "$JAVA_HOME/jre/lib/security/blacklisted.certs":
F="$JAVA_HOME/lib/security/blocked.certs"
head -1 "$F"
cat > /tmp/emudhra16.txt <<'L'
0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19
42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493
432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3
45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE
4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1
579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512
5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699
62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72
7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489
84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD
98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B
9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4
A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8
CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F
E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838
FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F
L
grep -c -x -F -f /tmp/emudhra16.txt "$F"Cómo leer el resultado:
- Ninguna de las dos raíces en cacerts: ese JDK no confía en la cadena, aunque no la bloquee. Es el caso de Temurin 21.0.13, con el que se probaron estos comandos, y de Temurin 25.0.5. En cambio, Temurin 8u504 y 11.0.32.1, las últimas versiones publicadas de esas ramas, sí traen emSign ECC Root CA - G3.
- Alguna raíz en cacerts y 16 en blocked.certs: el bloqueo está aplicado.
- Alguna raíz en cacerts y menos de 16 en blocked.certs: ese JDK acepta la cadena. Hay que actualizarlo o aplicar el blocked.certs de Oracle, y reiniciar las aplicaciones Java. El aviso de OpenJDK da como afectadas la 27.0.1, la 25.0.5, la 21.0.13, la 17.0.21, la 11.0.32.1, la 8u504 y anteriores.
- Un JDK de Debian o Ubuntu: su almacén es /etc/ssl/certs/java/cacerts, gestionado por el paquete ca-certificates-java. Tras actualizar los certificados del sistema, la página de Jackson indica regenerarlo con «touch /var/lib/ca-certificates-java/fresh» y «dpkg-reconfigure ca-certificates-java», como root.
Para validar el resultado, la misma prueba con jshell que usa la guía de remediación intenta conectar con un servidor de prueba de Jackson que sirve una cadena de estas intermedias:
"$JAVA_HOME/bin/jshell" <<'JAVA'
import java.net.*;
import javax.net.ssl.*;
var url = URI.create("https://dv-g3b-emudhra.we-have-a-problem.com/").toURL();
try {
var c = (HttpsURLConnection) url.openConnection();
c.setConnectTimeout(10000);
c.setReadTimeout(10000);
System.out.println("WARNING: TLS ACCEPTED: " + c.getResponseCode());
} catch (SSLHandshakeException e) {
System.out.println("TLS REJECTED: " + e.getMessage());
} catch (Exception e) {
System.out.println("INCONCLUSIVE: " + e);
}
/exit
JAVACon el bloqueo aplicado, la salida tiene que ser «TLS REJECTED» con «Untrusted certificate: CN=EM DV TLS CA - G3B». En Temurin 21 y 25, que no traen las raíces, el rechazo aparece como «PKIX path building failed». En la prueba, un JDK con las dos raíces en cacerts y el blocked.certs original respondió «WARNING: TLS ACCEPTED: 200».
Si no puedes actualizar ni sustituir el fichero, el aviso de OpenJDK propone además activar la comprobación de revocación con «-Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true», y en JDK 17 o posterior verificar los JAR firmados con «jarsigner -verify -revcheck -strict».
Frente al vector B: integridad de lo que descargas
Lo que se descarga tiene que comprobarse contra algo que el atacante no controla: un hash o un digest fijado antes, o una firma con una clave conocida. Por herramienta:
- Imágenes: referenciarlas por digest («imagen@sha256:…») en el FROM del Dockerfile y en los despliegues, no solo por tag. Y reconstruir sin caché, como pide el aviso de Python: «docker build --pull --no-cache».
- npm: instalar con «npm ci» a partir de un lockfile versionado. El integrity del lockfile protege lo que ya está fijado; un paquete nuevo hay que añadirlo desde una red y un cliente actualizados.
- Firmas de imágenes y artefactos: verificarlas con cosign/Sigstore antes de desplegar, exigiendo la identidad esperada. Una verificación que acepta cualquier firma no frena nada.
- apt y rpm: no instalar repositorios sin firma ni marcarlos como de confianza para saltarse el error.
- Scripts: no usar «curl | sh»; descargar el script, comprobar su hash o su firma si el proyecto la publica, y después ejecutarlo.
Para Python, el modo de comprobación de hashes de pip. Con --require-hashes, pip exige un hash para cada paquete y se detiene si lo descargado no coincide. uv genera el archivo con los hashes:
uv --version
mkdir -p /tmp/emudhra-uv && cd /tmp/emudhra-uv
echo "certifi>=2026.10.19" > requirements.in
uv pip compile requirements.in --generate-hashes -o requirements.txt
grep -A1 "^certifi" requirements.txtGenera los hashes con herramientas ya actualizadas y desde una red que controlas. Un hash calculado sobre una descarga interceptada fija el paquete equivocado.
La instalación con hashes queda así:
python3 -m venv /tmp/emudhra-hash && . /tmp/emudhra-hash/bin/activate
cd /tmp/emudhra-hash
cat > requirements.txt <<'REQ'
certifi==2026.10.19 \
--hash=sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b
REQ
python3 -m pip install --require-hashes -r requirements.txtSi el paquete descargado no es el esperado, pip se detiene antes de instalarlo. Con el hash alterado a propósito, la salida es esta:
ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE.
certifi==2026.10.19 from https://files.pythonhosted.org/packages/.../certifi-2026.10.19-py3-none-any.whl
Expected sha256 00d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b
Got 42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1bLos secretos del build no deben ir en argumentos de build ni en variables de entorno: la documentación de Docker dice que no son adecuados para eso y recomienda montajes de secretos.
Frente al vector D: la firma de código en Windows
Microsoft distribuye el bloqueo de estas intermedias por las actualizaciones automáticas de la lista de certificados de confianza de Windows, según la página de Jackson. Lo primero es que los equipos reciban esas actualizaciones.
Una firma válida no basta para fiarse de un binario. Antes de ejecutar software descargado, conviene comprobar quién lo firma: en PowerShell, Get-AuthenticodeSignature muestra el certificado del firmante. Un binario cuya cadena pase por EM OV CS CA - G3B o EM OV CS CA - G3C no debe ejecutarse.
Frente al vector E: fijar en quién confías
La defensa es no delegar la identidad del firmante en el almacén público:
- Verificar con una clave o un certificado fijados, compartidos fuera de banda, con HMAC o con el JWKS del emisor que tienes configurado, resolviendo la clave por «kid».
- No usar como clave de verificación el certificado que trae el propio token o mensaje.
- En signxml, pasar x509_cert= con el certificado esperado o ca_pem_file= con tu CA; cert_subject_name solo comprueba el nombre.
- Para mTLS, emitir los certificados de cliente con una CA privada y cargar solo esa CA en el servidor.
- Exigir el propósito (EKU) al verificar una cadena.
- Mantener en el servidor las validaciones de negocio: límites, autorización por rol y conciliación.
Qué revisar esta semana
- Qué runtime y qué almacén de confianza usa cada cliente que sale a internet, incluidos los jobs de CI y los agentes.
- Cuántos JDK hay en cada máquina y cada imagen, si los que contienen emSign ECC Root CA - G3 o AAA Certificate Services tienen ya las 16 huellas en blocked.certs y si sus aplicaciones se han reiniciado después.
- Si pip, uv y certifi están en las versiones mínimas del aviso de Python en cada imagen, y si las imágenes se han reconstruido sin caché.
- Si las imágenes se referencian por digest, si npm instala desde el lockfile y si los requirements de producción usan hashes.
- Si algún código desactiva la verificación TLS.
- Qué tokens han pasado por clientes sin actualizar y si compensa rotarlos.
- Si los equipos Windows reciben las actualizaciones automáticas de certificados.
- Qué decisiones automáticas dependen de datos externos sin firma, sin segunda fuente o sin límites.
- Qué verificadores de firmas o de certificados de cliente aceptan cualquier cadena que llegue al almacén público.
Con las raíces TLS al día, o con las 8 intermedias bloqueadas, el certificado forjado deja de valer; los hashes y las firmas añaden una segunda barrera para lo que se descarga.
Si quieres comentar estas comprobaciones en tus propios servicios o builds, puedes escribirnos desde la página de contacto; en el mensaje, menciona este artículo y el servicio o el pipeline que tienes en mente, sin secretos, tokens ni material operativo confidencial.
Fuentes
Fuentes consultadas el 23 de octubre de 2026. Ejecutado en Linux, sobre Debian, con la salida guardada: el inventario de versiones, el grep, la generación de hashes con uv, la instalación con hashes, correcta y alterada, y los comandos de Java sobre un JDK Temurin y sobre copias con las raíces añadidas, con el blocked.certs original y con las 16 huellas, incluida la conexión con jshell al servidor de prueba de Jackson. No ejecutado: docker, npm, cosign ni PowerShell, ni nada en macOS o Windows.
- Mozilla Bugzilla 2083449: informe de incidente
- Python Discourse: información para usuarios de Python
- Cloudflare Blog, 20/10/2026
- Google Security Blog, 20/10/2026
- Mozilla Security Blog, 20/10/2026
- Dennis Jackson: resumen del incidente
- Dennis Jackson: intermedias afectadas
- Dennis Jackson: raíces de las intermedias
- Comunicado de la CA sobre el incidente
- Node.js: avisos de seguridad de octubre de 2026
- OpenJDK: aviso de vulnerabilidades del 21/10/2026
- Oracle Security Alert CVE-2026-91403
- Oracle JDK 25.0.5.0.2: notas de versión
- Oracle JDK 8u511 b32: notas de versión
- Oracle JDK 8u511: notas de versión
- OpenJDK 27.0.1.0.2 en jdk.java.net
- Amazon Corretto 25: CHANGELOG
- Amazon Corretto 8: CHANGELOG
- Homebrew: fórmula openjdk
- OpenJDK: cacerts
- Docker Desktop release notes, 4.96.3
- RFC 7515: JSON Web Signature
- signxml: establecer la confianza
- cryptography: verificación X.509
- pip: modo de comprobación de hashes
- uv: referencia de la CLI
- Docker: build secrets
- Docker: build variables
Lecturas relacionadas
¿Necesitas aplicar esto a tu lanzamiento o workflow?
Solicita una revisión técnica y mapeamos el primer paso seguro.


