Cuidado con Certificados TLS No Verificados en PHP y Python

Share this…

Nosotros, los desarrolladores web, nos basamos en muchas APIs de terceros. Por ejemplo, estas APIs nos permiten aceptar pagos de tarjetas de crédito, integrar un sitio web a redes sociales, borrar la memoria caché del CDN. El protocolo HTTPS se utiliza para asegurar la conexión con el servidor de la API. Sin embargo, si su aplicación web no comprueba los certificados TLS, alguien con malas intenciones puede robar sus contraseñas o los números de tarjetas de crédito de sus clientes.

Seguridad-certificados-web

Cuando se aplica correctamente, el protocolo TLS proporciona cifrado y autenticación. La conexión entre el servidor y el servidor de la API está encriptada mediante un código simétrico (típicamente AES) para que otros no puedan leer su información. El servidor también confirma su identidad (se autentica) mediante el envío de un certificado X.509 al cliente. El cliente debe verificar la firma del certificado con la lista de certificados raíz conocidos, pero este paso es a menudo pasado por alto. Como resultado, un ataque man-in-the-middle se hace posible.

Si usted no comprueba el certificado, el atacante puede hacerse pasar por el servidor de la API, interceptar la información enviada en ambas direcciones, o incluso devolver mensajes falsos que el servidor de la API no le ha enviado. Este ataque se ha discutido anteriormente en el artículo The Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software escrito por Martin Georgiev y otros. Los autores descubrieron que muchos clientes escritos en las bibliotecas de API de Java y PHP no comprueban correctamente los certificados y se vuelven vulnerables a los ataques. Los autores probaron estas bibliotecas de clientes con un certificado autofirmado y un certificado válido que pertenecía a otro nombre de dominio. Consideran que el problema es: API SSL contrarios a la intuición (por ejemplo, CURLOPT_SSL_VERIFYHOST en cURL) y las bibliotecas SSL inseguras (función fsockopen en PHP).

Mediante la transmisión de cualquier información personal o financiera a través de HTTPS, asegúrese de que el certificado TLS se ha verificado correctamente. Hace dos años, IOActive probó 40 aplicaciones móviles de bancos y descubrió que el 40% de ellos eran vulnerables a un ataque MITM. Otro grupo de investigadores del Leibniz University of Hanover and Philipps University of Marburg ha descubierto que el 8 % de las aplicaciones Android populares no verifica certificados. Un ataque MITM pasivo contra estas aplicaciones móviles es muy real cuando se utiliza un punto de acceso de Wi-Fi público. El ataque también es posible en el caso de un servidor web acceder a una API de terceros.

PHP 5.6 ha solucionado algunos de los problemas de verificación de certificados.Python lo ha hecho también en las versiones 3.4.3 y 2.7.9. He probado las nuevas versiones para ver lo que habían y lo que no habían arreglado. También he probado certificados revoked and expired, y agregación de certificados (no incluido en la investigación de Georgiev et al.)

Pruebas

Vamos a usar los servidores HTTPS de prueba (trabajando en la hora de escribir) que desencadenan un error de seguridad en un navegador moderno:

  • https://revoked.grc.com (revoked certificate, see GRC Revocation Awareness Test);
  • https://tv.eurosport.com (the domain name does not match, see SSL Test Center by NetLock Ltd.);
  • https://qvica1g3-e.quovadisglobal.com (expired certificate, see QuoVadis Test Certificates);
  • https://self-signed.badssl.com (self-singed certificate, see BadSSL.com);
  • https://rc4.badssl.com/ (outdated RC4 cipher);
  • https://dh480.badssl.com/ (a weak Diffie-Hellman key).

El sitio web BadSSL.com contiene otros casos de prueba que pueden ser útiles para probar nuevas aplicaciones .

Los scripts de prueba conectan a cada servidor, usando una configuración TLS por defecto. Usted puede ejecutar estos scripts en su instalación para ver si hay problemas.

Revoked Expired Self-signed Bad domain RC4 DH480
PHP 5.5 cURL X X
PHP 5.5 streams X X X X X X
PHP 5.6 cURL X
PHP 5.6 streams X
Python 2.7.6 (urllib, urllib2, httplib) X X X X X X
Python 2.7.6 (Requests) X X X X X
Python 2.7.10 (urllib, urllib2, httplib, Requests) X X
Python 3.3.0 (urllib.request, http.client) X X X X X X
Python 3.3.0 (Requests) X X
Python 3.4.3 (urllib.request or http.client without context) X X X X
Python 3.4.3 (urllib.request or http.client with context, Requests) X X
Google Go (net/http) X

Todas las implementaciones de lenguajes de programación no funcionan para verificar si el certificado se revoca.

La implementación de TLS en PHP 5.5 y anteriores se rompe cuando se utilizan funciones stream (fsockopen, fopen, stream_socket_client, stream_socket_enable_crypto, o file_get_contents). Se debe usar funciones cURL yactualizar para PHP 5.6 lo antes posible. Tenga en cuenta que PHP 5.5 permite el cifrado RC4 desactualizado, aunque se use cURL.

Para Python, la situación es aún más complicada y poco documentada. La mejor solución sería actualizar para las versiones 3.4.3 y 2.7.9 o más nuevas y siempre usar el context parameter.

Configuración TLS Recomendada para PHP

Si se puede instalar una versión más nueva de PHP en su servidor, actualice para PHP 5.6 o superior. Esto va a resolver todos los problemas de verificación, excepto los certificados revocados.

Si no se controla el software del servidor (por ejemplo, se está ejecutando en un host compartido, o su aplicación PHP es utilizada por miles de personas en todo el mundo),use la biblioteca cURL con las siguientes opciones:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

Estas opciones ya están ajustadas a los valores correctos, así como cURL 7.10, pero hay que configurar estas opciones para las versiones más antiguas.

Streams HTTPS en PHP 5.5 o más antiguas son inseguras y nunca deben ser utilizadas. Para rechazar los certificados de firma propia, configure el context option ‘verify_peer’ para TRUE. Sin embargo, el atacante también puede utilizar un certificado de otro dominio. Algunas autoridades de certificación ofrecen certificados DV gratuitos, entonces usarlos no costaría nada al atacante.

Si su código es synchronous, es posible cambiar las funciones stream(fsockopen e outras) por funciones cURL. Si usted usa stream (asynchronous) I/O non-blocking con TLS o HTTPS, actualice para PHP 5.6 porque ese es inseguro en sus versiones antiguas.

Verificación de Certificado TLS en Python

Versions antiguas de Python (antes de 2.7.9 o 3.4.3) no verifican certificados. Hay una biblioteca de terceros, que hace peticiones para verificar certificados TLS. Sin embargo, no funcionan para todas las versiones de Python. En mis pruebas,peticiones no pueden rechazar un certificado autofirmado o expirado en versiones anteriores a Python 2.7.6 (que es una versión soportada de acuerdo con la documentación de las Peticiones).

Las nuevas versiones de Python deberían haber resuelto estos problemas de verificación de TLS, pero hay algunas advertencias. Si está utilizando un Python 2.7.9 o versión más avanzada, el certificado será activado de forma predeterminada:

f = urlopen(url)

Sin embargo, este código permite erróneamente cualquier certificado más antiguo que Python 3.4.3 o superior. Con Python 3.x, usted debe utilizar el context parameter para verificar el certificado:

f = urlopen(url, context = ssl.create_default_context())

Haga lo mismo para http.client.HTTPSConnection constructor. Este hecho no se menciona en ninguna documentación. El change log dice que cada certificado se comprueba “por defecto”; a referencia a la biblioteca también, no dice nada.

La implementación del Python TLS también le permite llaves Diffie-Hellman débiles y cipher RC4 desactualizados en muchas versiones de Python (vea la tabla arriba).

Agregando Certificados

Se puede reforzar la seguridad de la TLS permitiendo un número limitado de certificados. Por ejemplo, si un servidor de API siempre debería devolver el mismo certificado, se puede poner este código en su aplicación.

PHP 5.6 y más nuevos ofrecen el context option peer_fingerprint (pero, usan un hash SHA-1 débil). Al usar cURL, se puede usar el parámetro CURLINFO_CERTINFO paratener el certificado:

if (defined('CURLOPT_CERTINFO')) {
    curl_setopt($ch, CURLOPT_CERTINFO, TRUE);
}

curl_exec($ch);

if (defined('CURLINFO_CERTINFO')) {
    $certinfo = curl_getinfo($ch, CURLINFO_CERTINFO);
    echo $certinfo[0]['Cert'];
}

El certificado puede ser hashed y comparado (usando hash_equals) con el valor conocido. Tenga en cuenta que este código requiere cURL 7.19.1 o posterior con OpenSSL (por ejemplo, no va a funcionar con oOS X). En Google Go, se obtiene el certificado a partir del array Response.TLS.VerifiedChains. En Python, esta tarea implica el uso de un módulo de ssl de nivel inferior; urllib no tiene API documentado para añadir o retirar el certificado.

Twitter recomienda otro enfoque para sus APIs: verifique con el número mínimo de certificados root. CDN de Twitter usa muchos certificados firmados por Symantec/Verisign o Digicert, entonces no se puede añadir cualquier certificado. En este caso, se podría construir un archivo de certificados raíz personalizado y luego comprobar con sólo los certificados raíz. También se puede hacer lo mismo con PHP (vea la opción CURLOPT_CAINFO), Python y Google Go.

Verificaciones de Revogaciones

PHP, Python y Google Go no verifican las revocaciones por defecto, incluso ni la biblioteca CURL lo hace. Si el certificado ha sido infectado y revocado por su propietario, nunca se sabrá.

Escribir su propio código de comprobación de revocación no es realista; sería necesario un criptógrafo de talento. PHP tiene la opción CURLOPT_CRLFILE, pero usted tendría que descargar el archivo CRL y verificar su firma. OCSP stapling no es compatible con PHP. Google Go sólo devuelve una respuesta stapled OCSP raw no Response.TLS.OCSPResponse. Se necesitaría una gran cantidad de trabajo para desarrollar una función de comprobación de revocación a partir de estas herramientas.

Conclusión

El artigo The Most Dangerous Code in the World (El Código Más Peligroso del Mundo) se publicó en 2012. Después de eso, PHP se ha fijado la mayoría de sus problemas con TLS; Python también tiene una API no intuitiva y permite el uso de sistemas de cifrado obsoletos.

Fuente:https://blog.sucuri.net