Gravísimo error en sistemas operativos de Apple invalida el TLS/SSL. Goto fail

lunes, 24 de febrero de 2014

Apple ha cometido un gravísimo (que casi roza el ridículo) error en su implementación de SecureTransport, una capa del sistema de gestión de TLS/SSL como ya han contado nuestros compañeros en Seguridad Apple. En resumen, introduciendo por error una línea de código, ha invalidado por completo el SSL en iOS y Mac OS X durante unos dos años, la criptografía más fundamental y pilar de la confianza web. Tan simple y absurda, que podría parecer intencionada. Veamos qué ha pasado y sus consecuencias.

Apple lanzó una actualización muy urgente para iOS (no aún para Mac OS X que continúa con el problema). Como siempre, sin demasiados detalles y con vagas referencias a SSL. Pero poco después se descubrieron los detalles del problema.

Código abierto en http://opensource.apple.com/source/Security/Security-55471/libsecurity_ssl/lib/sslKeyExchange.c

Se observa una línea repetida "goto fail". La indentación es incorrecta y es lo que puede llevar a confusión, pero solo el primer "goto fail" está ligado al if. El segundo "goto fail" se ejecuta siempre, ocurra lo que ocurra con los if anteriores. Lo que quiere decir, que hay una parte del código a la que nunca se llega, y en la que se realiza una verificación importante relativa al intercambio de claves en el protocolo SSL. En otras palabras, si la operación SLHashSHA1.update es válida y "err" toma un valor válido, la verificación de la clave nunca dará error, aunque el resto de valores no se hayan comprobado. Para entender el fallo, nos ponemos un poco en situación con el protocolo SSL

El protocolo SSL

En TLS/SSL se puede utilizar Diffie/Hellman (DH) como protocolo de intercambio seguro de claves en un canal inseguro. Así, se negocia una clave DH efímera (se descarta después de cada uso) que permite el cifrado simétrico de la comunicación entre navegador y servidor. Pero TLS/SSL no solo permite cifrado sino que también autenticación. Para eso se utiliza el protocolo RSA y por supuesto los certificados. Estos se encargan de firmar la comunicación, es decir, garantizar que vienen del servidor correcto.

El fallo se produce en el momento de enviar el mensaje ServerKeyExchange. En este momento es cuando el servidor le ofrece al navegador o cliente esa clave efímera, y la firma con su clave privada que, avalada por su certificado, no dejan lugar a dudas de que proviene de quien dice provenir. Pero, debido al error, esto no tiene por qué ser así. Se le puede comunicar una clave efímera, pero no firmarla, o firmarla con un certificado incorrecto. El navegador o cliente nunca se quejaría. En otras palabras, podrías conectarte a una página por https que no fuera la que dice ser, pero ningún aviso en el navegador te diría lo contrario. Limpiamente un atacante podría obtener toda la información de la comunicación.

Eso sí, obviamente la red en la que se navega tendría que estar ya en cierta manera manipulada por el atacante, puesto que la víctima tendría que ser redirigida a nivel de dominio. Quizás, es una de las razones por las que Apple ha priorizado la solución para iOS. También por lo que se ha lanzado la teoría de la conspiración que siempre surge cuando se habla de la capacidad de descifrar las comunicaciones cifradas. Los gobiernos podrían tener la capacidad de desviar a los usuarios a servidores diferentes, puesto que podrían controlar los DNS de red. De hecho, algunos lo hacen.

¿Hay algo que mitigue el problema?

¿Y el certificate pinning? Ante este fallo, no serviría. El atacante podría enviar la cadena de certificados válidos, pero aun así una firma de la clave efímera perteneciente a otro, y el cliente SSL no se quejaría. ¿Y si no se usa DH para el intercambio de claves? No importaría, porque en SSL el servidor suele poder negociar qué suite de cifrado se quiere usar. Solo podrían librarse los navegadores o clientes que fuercen TLS 1.2 y no degraden a uno anterior aunque el servidor se lo solicite. Pocos navegadores lo hacen porque limitaría su capacidad de conexión con servidores que no lo soportan.

¿Lecciones aprendidas?

El bug dará para muchas discusiones, análisis (y mofas) durante mucho tiempo. Pocas veces se observa un fallo tan importante y simple. Quizás remite al fallo criptográfico de Debian ocurrido en 2008 explicado en detalle en esta conferencia de Luciano Bello. En primer lugar, sorprende (bueno, quizás no tanto) que un fallo tan absurdo haya ocurrido en código abierto. ¿Cuánto tiempo estuvo ahí? El código desvela también malas prácticas en el estilo de programación (deberían haberse utilizado llaves para contener el if, evitar los gotos...). La indentación, que aunque se considera una buena práctica, ha jugado una mala pasada. Incluso desvela malas prácticas de compilación. Existen técnicas y comandos a la hora de compilar que permiten lanzar una alerta en tiempo de compilación cuando si se detecta código al que nunca se puede llegar. Desde luego, Apple ha cometido un error gravísimo y ha puesto no solo en peligro a sus usuarios, sino que ha dejado en entredicho la calidad de sus pruebas de software.

¿Soy vulnerable?

Si se usa Mac OS X Mavericks, probablemente sí. Si usas iOS, se debe actualizar. El fallo fue introducido en la versión 6.0 de iOS, aparecido a mediados de 2012. El nivel de paranoia aumenta cuando se rumorea que Apple se adhirió a PRISM en octubre de ese año. En escritorio, se encuentra en Mavericks, pero no en anteriores. Ya existen páginas para comprobar si se es vulnerable. Aquí, y aquí.


Fuente: https://twitter.com/0xabad1dea/status/437291365508976640

Por último, advertir de que el fallo no solo afecta al navegador. El problema está en librerías a bajo nivel a las que acuden todo tipo de clientes, y por tanto la vulnerabilidad va mucho más allá. Por ejemplo, pensemos en el proceso de actualización de software, el uso de correo, mensajería, etc. Cualquier cosa que utilice SSL para cifrar su tráfico y no necesariamente nativa del sistema operativo, sino que simplemente utilice sus librerías. 

Sergio de los Santos
ssantos@11paths.com

2 comentarios:

  1. Es que menudas prácticas más horrendas de programación! Primero... usando goto en programación estructurada (facepalm)... Aunque haya que "trabajar un poco más" hay que utilizar if y else. Es decir:

    En vez de: if (condición de error 1) goto error; if (condición de error 2) goto error; código sin error; return -1; error: código para error; return 0;

    Hay que hacer:
    if (condición de error 1 || condición de error 2 || condición de error 3) {código con error} else {código sin error}

    Segunda cagada (aunque de menor grado que usar goto). Lo dijo mi profesor de Ingeniería de software y cuanta razón tenía, es una práctica altamente recomendada en los lenguajes de la familia C utilizar siempre if (condición) {código} else {código} aunque solamente tengamos una única línea, es decir, no utilizar if (condición) línea; else línea; Es mucho más entendible para el programador (véase el caso) y además es reutilizable.

    Un saludo

    ResponderEliminar
  2. Pido disculpas porque hay una errata en mi anterior comentario. Dónde pone: goto error; código sin error; return -1; error: código para error; return 0; debería poner: goto error; código sin error; return 0; error: código para error; return -1; Un saludo.

    ResponderEliminar