Let’s Encrypt revoca tres millones de certificados por culpa de (bueno, casi) un “&” en su código

David García    9 marzo, 2020
Let's Encrypt revoca tres millones de certificados por culpa de un "&" en su código

Let’s Encrypt, que hace poco celebraba su certificado mil millones, ha metido la pata en su código y ha comprometido (aunque no demasiado, como veremos más adelante) la seguridad de sus certificados. No es tan grave como el problema con el que nos encontramos en 2008 en Debian. En aquel momento, podíamos conocer la clave privada de cualquier certificado calculado con Debian gracias a muy poca fuerza bruta.

Ahora, Let’s Encrypt ha facilitado durante 30 días en cierta forma una, ya de por sí, remota posibilidad: que se cree un certificado con el mismo dominio en otra CA. De hecho, lo más interesante no son las consecuencias, sino el fallo (un poco simple) en el código que ha permitido este problema.

A principios de marzo, Let’s Encrypt ha tenido que revocar más de 3 millones de certificados (un 2,6% de los activos) por un fallo importante (aunque no impactante) en su plataforma Boulder. Está escrito en Go y es el sistema responsable de comprobar que la persona que solicita el certificado es su dueña. Una de las comprobaciones es acudir al registro CAA de los DNS del dominio para el que se emite el certificado. Este registro DNS debe contener qué CAs son las de tu preferencia. Por ejemplo, no quiero que la CA X emita un certificado para mí jamás o sólo quiero que la CA Y emita certificados para mi dominio.

¿Cómo se produjo el fallo?

Desde 2017 las autoridades deben, por su lado, comprobar este campo antes de emitir un certificado solicitado, y si se contradice, deben abortar la misión. Así, pongamos que un atacante intenta de alguna forma crear un certificado para mi dominio usando otra CA; un problema más con el que se encontrará es que si el dueño no lo ha establecido así en sus DNS, esta CA no podrá hacerlo.

Pero hasta ahora, Boulder no lo comprobaba bien por un fallo programático en un bucle. Cuando un certificado contenía N dominios en su interior (algo habitual), el software comprobaba N veces un solo dominio contra su CAA. El fallo se fundamenta en un aspecto del lenguaje Go que se debe conocer para no cometer un error con la toma de referencias en variables definidas dentro de un bucle. Let’s Encrypt considera que esta información del registro DNS es buena por 30 días, y luego vuelve a comprobar si la CA está autorizada para emitir ese certificado. Por tanto, si no lo comprobaba bien, se podría haber emitido un certificado falso hasta que, potencialmente, 30 días después, se volviese a comprobar.

En realidad, para poder aprovechar ese fallo, un atacante debía intentar romper otras muchas reglas, porque no se puede emitir un certificado válido para un dominio si no eres el legítimo dueño de él. Para ello, las CAs tiene muchas fórmulas diferentes para comprobarlo, algunas más relajadas que otras. Centrémonos en el código. En esta imagen vemos el código que causaba el fallo:

Código que generaba el fallo en Boulder

El código itera sobre una tabla hash. Al llamar a la v como una referencia, la dirección de v siempre es la misma, porque no representa un valor, sino una dirección de memoria que apunta a un objeto. Se podía haber hecho una copia de v como se hace de k quitando el & (que es lo que se ha hecho en el código corregido) con lo que serían objetos diferentes en cada iteración y no diferentes objetos en la misma dirección.

En el propio código original avisaban con un comentario: “hacemos una copia de k porque en cada loop se reasignará”. Y lo mismo tenían que haber hecho con la v, sólo que no lo hicieron. Ahora, tras el parche, sí se pasa a la función “modelToAuthzPB” el parámetro v por valor, por lo que cambiará de forma efectiva en cada iteración y llamada a “modelToAuthzPB” proporcionando el resultado correcto. Sin el parche, authz2ModelMapToPB creaba un mapa cuyas claves sí son la lista de dominios en el certificado pero cuyo contenido tenían todos mismo el mismo IdentifierValue.

Conclusiones

Como hemos visto, se trata de un fallo grave, pero no catastrófico. Let’s Encrypt ha actuado de forma transparente y diligente. El fallo se ha corregido en tiempo récord y los certificados han sido revocados. De nuevo, recordemos que la programación es complicada, sea de código abierto o no.

Resulta curioso cómo lenguajes de nueva generación que tratan de solventar los problemas de la gestión manual de memoria siguen teniendo aristas sin pulir. En este caso no se trata de una mala práctica en sí, ya que pasar por referencia ahorra una gran cantidad de memoria y de operaciones de copia (más aún si las estructuras son pesadas), pero sí de un comportamiento arriesgado en el que a menos que conozcas “dónde está la piedra” puedes volver a tropezar con ella.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *