Ignorar la verificación del certificado de seguridad SSL en las solicitudes de Python

Jay Shaw 10 octubre 2023
  1. Comprender el motivo detrás de las comprobaciones de seguridad SSL y por qué falla
  2. Ignorar la verificación de seguridad SSL en Python
  3. Conclusión
Ignorar la verificación del certificado de seguridad SSL en las solicitudes de Python

Acceder a una URL sin un certificado SSL seguro genera advertencias de excepción cuando se le envían solicitudes HTTP. Muchas veces, el certificado SSL de estas URL caduca, lo que crea todo tipo de problemas de seguridad.

Si la información no es confidencial, estas advertencias pueden atenuarse cuando los programas usan solicitudes en Python. Este artículo proporcionará varias formas de desactivar las comprobaciones de certificados de seguridad mediante solicitudes.

Comprender el motivo detrás de las comprobaciones de seguridad SSL y por qué falla

Si un programa utiliza solicitudes de Python para obtener solicitudes de una URL cuyo certificado SSL ha caducado, genera dos excepciones. El siguiente programa muestra cuáles son esas excepciones.

escenario 1

Este programa utiliza una URL proporcionada por la comunidad SSL con un certificado de seguridad caducado con fines de prueba. Cabe señalar que las excepciones de seguridad SSL solo surgen con URL que tienen certificados SSL caducados.

Las peticiones de Python no generan ninguna excepción con URLs con certificado SSL válido o revocado. Por lo tanto, este artículo se centrará principalmente en las URL con certificados de seguridad caducados.

El siguiente ejemplo muestra un programa simple que importa solicitudes en la primera línea. La segunda línea del programa envía una solicitud post a la URL para modificar las apariciones de 'bar' como 'baz'.

Se hace para enviar una solicitud de publicación a la URL y no tiene ningún otro significado dentro del programa.

import requests

requests.post(url="https://expired-rsa-dv.ssl.com/", data={"bar": "baz"})

La ejecución de este script de Python arroja excepciones SSLError.

....
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='expired-rsa-dv.ssl.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:841)'),))

Esta es la primera excepción a tener en cuenta al aprender a deshabilitar las comprobaciones de certificados de seguridad mediante solicitudes.

Escenario 2

Este programa desactiva la verificación del certificado SSL usando verify=False para deshabilitar la verificación del certificado de seguridad usando requests.

import requests

requests.post(url="https://expired-rsa-dv.ssl.com/", data={"bar": "baz"}, verify=False)

La biblioteca de solicitudes está construida de manera que puede desactivar la verificación de certificados SSL, pero el programa arroja otra excepción con enlaces que tienen certificados SSL caducados.

InsecureRequestWarning: Unverified HTTPS request is being made to host 'expired-rsa-dv.ssl.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  InsecureRequestWarning,

Es necesario tratar estas dos excepciones para deshabilitar las comprobaciones de certificados de seguridad mediante solicitudes.

Ignorar la verificación de seguridad SSL en Python

Esta sección explicará varios métodos que deshabilitan la verificación del certificado de seguridad usando solicitudes o brindan una solución al problema. Cada método tiene su propósito.

Crea un Monkey Patch para la biblioteca requests

Si una biblioteca de terceros requiere que las comprobaciones de seguridad estén deshabilitadas, la biblioteca de solicitudes puede ser monkey patched. Se utiliza un administrador de contexto para parchear para deshabilitar las comprobaciones de certificados de seguridad mediante solicitudes.

Después de parchear las solicitudes, el campo verificar recibe un valor False de forma predeterminada, lo que suprime la advertencia. Se lanza otra advertencia cuando se usa verify=false, como se explica en el escenario 2 de la sección anterior.

Luego, el parche agregará un bloque de manejo de excepciones para deshabilitar la verificación del certificado de seguridad mediante solicitudes y suprimirá las advertencias. Será más fácil de entender con los siguientes ejemplos.

El siguiente programa parchea la biblioteca de solicitudes. Entendamos lo que hace este código.

Importaciones:

  1. advertencias: este paquete de biblioteca es un subpaquete de la biblioteca Excepciones.
  2. contextlib: esta biblioteca de Python se utiliza para parchear la biblioteca de solicitudes.
  3. solicitudes: el paquete de biblioteca de solicitudes de Python.
  4. urllib3: Es un módulo que maneja solicitudes HTTP y URLs en Python. Esta biblioteca se utiliza para importar un submódulo InsecureRequestWarning, que genera una excepción para los certificados SSL caducados.

Parche:

Al principio, el programa guarda la configuración de entorno predeterminada de la biblioteca requests en una variable: old_merge_environment_settings. Esta variable se utilizará para devolver las solicitudes a su estado predeterminado después de que se cierren los adaptadores abiertos.

old_merge_environment_settings = requests.Session.merge_environment_settings

El método no_ssl_verification se crea y se adorna con @contextlib.contextmanager. Se crea una nueva variable opened_adapters y se le asigna un set().

Un conjunto es un tipo de datos que almacena elementos en un formato desordenado.

@contextlib.contextmanager
def no_ssl_verification():
    opened_adapters = set()

Dentro del método no_ssl_verification, se crea otro método anidado merge_environment_settings. Este método tiene seis parámetros, similares a merge_environment_settings del módulo requests, y actuará como parche para el módulo original.

def merge_environment_settings(self, url, proxies, stream, verify, cert):

Dentro del método, la variable opened_adapters se actualiza con el par de adaptadores coincidentes del parámetro url. Cada conexión se realiza con algún par de adaptadores coincidentes, que se devuelven en este paso.

Dado que la verificación solo ocurre una vez por conexión, debemos cerrar todos los adaptadores abiertos una vez que hayamos terminado. De lo contrario, los efectos de verify=False duran después de que finalice este administrador de contexto.

def merge_environment_settings(self, url, proxies, stream, verify, cert):

    opened_adapters.add(self.get_adapter(url))

Es importante recordar la primera sección del artículo para comprender la siguiente línea de código. Cuando la función requests.post se usó en la URL con un certificado SSL caducado, arrojó dos excepciones.

La primera excepción fue causada por verificar, que se establece con un valor True. Aunque el campo verificar era conmutable, se le podía dar un valor False.

La segunda excepción fue una entidad no mutable que impidió que el programa estableciera una conexión.

El siguiente código modifica el campo verificar para que tenga un valor False por defecto para resolver este problema. Se crea una nueva variable settings para ejecutar este paso, que se asigna con datos de la variable old_merge_environment_settings.

Una vez que los datos se almacenan dentro de la variable configuración, el campo verificar se convierte en False.

Esto cambia el valor predeterminado de verificar. Por último, se devuelve esta variable.

settings = old_merge_environment_settings(self, url, proxies, stream, verify, cert)
settings["verify"] = False

return settings

Los datos del método actualizado merge_environment_settings se asignan a la función requests.Session.merge_environment_settings. Esto asegurará que el campo verificar tenga un valor False por defecto.

requests.Session.merge_environment_settings = merge_environment_settings

Nos hemos ocupado de la primera excepción. La segunda excepción, que no es mutable, se resolverá mediante un bloque de manejo de excepciones.

Entendamos lo que hace el código aquí.

En un primer momento, dentro del bloque try, se crea un bloque with que recoge todas las advertencias emitidas. Dentro de este bloque with, la función warnings.simplefilter se usa para dar un valor ignorar al submódulo urllib3 InsecureRequestWarning.

Como aprendimos en la sección de importaciones, el submódulo InsecureRequestWarning genera una excepción para los certificados SSL que caducan; asignarle un valor ignorar omitirá la segunda excepción causada.

try:
    with warnings.catch_warnings():
        warnings.simplefilter('ignore', InsecureRequestWarning)
        yield

Dentro del bloque finally, la configuración original almacenada dentro de old_merge_environment_settings se vuelve a asignar a la función requests.Session.merge_environment_settings.

Como todos los adaptadores abiertos deben cerrarse antes de que el programa salga, se crea un bucle for que itera la cantidad de veces que los adaptadores abiertos están dentro del programa.

Todos los adaptadores se cierran usando la función adapter.close() usando un bloque try-except; dentro del bloque excepto, se pasa la iteración.

for adapter in opened_adapters:
    try:
        adapter.close()
    except:
        pass

El código completo se da a continuación.

import warnings
import contextlib

import requests
from urllib3.exceptions import InsecureRequestWarning

old_merge_environment_settings = requests.Session.merge_environment_settings


@contextlib.contextmanager
def no_ssl_verification():
    opened_adapters = set()

    def merge_environment_settings(self, url, proxies, stream, verify, cert):

        opened_adapters.add(self.get_adapter(url))

        settings = old_merge_environment_settings(
            self, url, proxies, stream, verify, cert
        )
        settings["verify"] = False

        return settings

    requests.Session.merge_environment_settings = merge_environment_settings

    try:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", InsecureRequestWarning)
            yield
    finally:
        requests.Session.merge_environment_settings = old_merge_environment_settings

        for adapter in opened_adapters:
            try:
                adapter.close()
            except:
                pass

Usa Monkey Patch en Python

El parche de mono se puede invocar utilizando el método no_ssl_verification(). Cualquier solicitud enviada bajo este método seguirá las directivas del parche de mono y deshabilitará las verificaciones de certificados de seguridad usando solicitudes.

Veamos un ejemplo.

El método no_ssl_verification se llama bajo un bloque with. Esto ejecutará el método dentro del bloque y luego se cerrará cuando el compilador salga del bloque.

Dentro del bloque, se envía una llamada requests.get a la URL con un certificado SSL caducado. Con la configuración predeterminada, esto habría causado un lanzamiento de excepción, pero esta vez la llamada requests.get se envía con éxito.

Se envía otra solicitud con las solicitudes, donde el campo verificar se establece en True. A diferencia del escenario predeterminado, esta vez no se lanza ninguna excepción de error.

with no_ssl_verification():
    requests.get("https://expired-rsa-dv.ssl.com/")
    print("Modifications working Properly")

    requests.get("https://expired-rsa-dv.ssl.com/", verify=True)
    print("`Verify=true` has its meaning changed")

Producción :

Modifications working Properly

`Verify=true` has its meaning changed

Se vio que Verify=False tenía las directivas para restablecer el parche con la configuración predeterminada. Cuando este campo se configuró dentro de requests.get(), el parche del mono se restablece a la configuración predeterminada, lo que permite generar excepciones de error.

requests.get("https://expired-rsa-dv.ssl.com/", verify=False)
print("It resets back")

Producción :

connectionpool.py:1052: InsecureRequestWarning: Unverified HTTPS request is being made to host 'expired-rsa-dv.ssl.com'.
  InsecureRequestWarning,
It resets back

El submódulo solicitudes sesión también se puede utilizar con el parche de mono.

En una variable llamada sesión, se carga la función solicitudes.Sesión(). El valor session.verify se establece en True.

Dentro de un bloque with, la función session.get() se usa para enviar la solicitud get a la URL, y el campo verify se establece en True. Una declaración de print imprime un mensaje si se realiza la conexión.

session = requests.Session()
session.verify = True

with no_ssl_verification():
    session.get("https://expired-rsa-dv.ssl.com/", verify=True)
    print("Works in Session")

Producción :

Works in Session

Cuando el administrador de contexto sale, este código cierra cualquier adaptador abierto que maneje una solicitud parcheada. Dado que requests mantiene un grupo de conexiones para cada sesión y la validación del certificado solo ocurre una vez por conexión, ocurrirán eventos inesperados como los siguientes.

try:
    requests.get("https://expired-rsa-dv.ssl.com/", verify=False)
except requests.exceptions.SSLError:
    print("It breaks")

try:
    session.get("https://expired-rsa-dv.ssl.com/")
except requests.exceptions.SSLError:
    print("It breaks here again")

Producción :

C:\Users\Win 10\venv\lib\site-packages\urllib3\connectionpool.py:1052: InsecureRequestWarning: Unverified HTTPS request is being made to host 'expired-rsa-dv.ssl.com'.
  InsecureRequestWarning,
It breaks here again

Use urllib3 para deshabilitar las advertencias en Python

Si el programa requiere deshabilitar las verificaciones de certificados de seguridad sin usar “solicitudes” o parches mono, entonces usar urllib3 proporciona una solución simple.

Importa requests y urllib3, y desde urllib3 importa el submódulo InsecureRequestWarning. La advertencia se deshabilita mediante la función urllib3.disable_warnings.

La instrucción requests.post() se coloca debajo del bloque try, y el campo verify se establece en False. Esto deshabilitará la comprobación de seguridad de los certificados de seguridad caducados.

Dentro del bloque excepto, aparece SSLError con un mensaje de error.

import requests
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

try:
    requests.post(
        url="https://expired-rsa-dv.ssl.com/", data={"bar": "baz"}, verify=False
    )
    print("Connection made successfully")

except requests.exceptions.SSLError:
    print("Expired Certificate")

Producción :

Connection made successfully

Conclusión

Este artículo explica varios métodos para deshabilitar las comprobaciones de certificados de seguridad mediante solicitudes en Python. El lector, a través del artículo, puede desactivar fácilmente los controles de seguridad.

Artículo relacionado - Python Requests