Búsqueda de vulnerabilidades en el código Python utilizando la herramienta Bandit de código abierto





Probablemente todos los desarrolladores hayan escuchado que necesitan escribir código limpio. Pero es igualmente importante escribir y usar código seguro.



Los desarrolladores de Python generalmente instalan módulos y paquetes de terceros para no reinventar la rueda, sino para usar soluciones probadas y listas para usar. Pero el problema es que no siempre se prueban a fondo para detectar vulnerabilidades. 



Los piratas informáticos suelen utilizar estas vulnerabilidades para los fines que se conocen. Por lo tanto, debemos ser capaces de registrar al menos los hechos de intrusión en nuestro código. Mejor aún, corrija las vulnerabilidades con anticipación. Para hacer esto, primero debe encontrarlos usted mismo en el código usando herramientas especiales.



En este tutorial, veremos cómo pueden aparecer las vulnerabilidades incluso en un código muy simple y cómo usar la utilidad Bandit . para encontrarlos.



Las vulnerabilidades más comunes en el código Python 



Probablemente hayas oído hablar de hackeos de sitios web grandes y robo de datos de usuario. Quizás te hayas encontrado personalmente con algún tipo de ataque. A través de vulnerabilidades en nuestro código, los atacantes pueden obtener acceso a los comandos o datos del sistema operativo. Algunas funciones o paquetes de Python pueden parecer seguros cuando los usa localmente. Sin embargo, al implementar un producto en un servidor, abren puertas a los piratas informáticos.



Los marcos modernos y otras herramientas de desarrollo de software inteligente que tienen protección incorporada hacen frente más o menos a los ataques más comunes. Pero está claro que no con todo el mundo y no siempre.



Inyección de equipo (inyección de comando)



La inyección de comandos es un tipo de ataque, cuyo propósito es ejecutar comandos arbitrarios en el sistema operativo del servidor. El ataque se desencadena, por ejemplo, cuando se inicia un proceso utilizando las funciones del módulo de subproceso , cuando los valores almacenados en las variables del programa se utilizan como argumentos. 



En este ejemplo, usamos el módulo de subproceso para realizar un nslookup y obtener la información del dominio:



# nslookup.py

import subprocess

domain = input("Enter the Domain: ")

output = subprocess.check_output(f"nslookup {domain}", shell=True, encoding='UTF-8')

print(output)
      
      





¿Qué podría salir mal aquí?



El usuario final debe ingresar al dominio y el script debe devolver el resultado del comando nslookup. Pero, si también ingresa el comando ls junto con el nombre de dominio separado por un punto y coma , se ejecutarán ambos comandos:



$ python3 nslookup.py

Enter the Domain: stackabuse.com ; ls

Server:         218.248.112.65

Address:        218.248.112.65#53

Non-authoritative answer:

Name:   stackabuse.com

Address: 172.67.136.166

Name:   stackabuse.com

Address: 104.21.62.141

Name:   stackabuse.com

Address: 2606:4700:3034::ac43:88a6

Name:   stackabuse.com

Address: 2606:4700:3036::6815:3e8d

config.yml

nslookup.py
      
      





Con esta vulnerabilidad, puede ejecutar comandos a nivel del sistema operativo (después de todo, tenemos shell = true ).



Imagínese lo que sucedería si un atacante, por ejemplo, emite un comando cat para / etc / passwd, que revelará las contraseñas de los usuarios existentes. Por lo tanto, usar el módulo de subprocesos puede ser muy arriesgado.



inyección SQL



La inyección SQL es un ataque que construye una declaración SQL que contiene consultas maliciosas a partir de la entrada del usuario. Debido al uso activo de ORM, el número de tales ataques ha disminuido significativamente. Pero si todavía tiene fragmentos escritos en SQL puro en su base de código, necesita saber cómo se construyen esas consultas SQL. ¿Qué tan seguros son los argumentos que valida y proporciona a la solicitud?



Consideremos un ejemplo:



from django.db import connection

def find_user(username):

    with connection.cursor() as cur:

        cur.execute(f"""select username from USERS where name = '%s'""" % username)

        output = cur.fetchone()

    return output
      
      





Aquí todo es simple: pasamos, por ejemplo, la cadena "Foobar" como argumento. La cadena se inserta en la consulta SQL, lo que da como resultado:



select username from USERS where name = 'Foobar'
      
      





Lo mismo que en el caso de la inyección de comandos - si alguien pasa el personaje ” ; ", Podrá ejecutar varios comandos. Por ejemplo, agreguemos la línea “ '; USUARIOS DROP TABLE; - "y obtén:



select username from USERS where name = ''; DROP TABLE USERS; --'
      
      





Esta consulta eliminará toda la tabla USERS. ¡UPS!



Observe el guión doble al final de la solicitud. Este es un comentario que neutraliza el siguiente carácter " ' ". Como resultado, el comando select se ejecutará con el argumento “ '' ” en lugar del nombre de usuario, y luego se ejecutará el comando DROP , que ya no es parte de la cadena.



select username from USERS where name = '';

DROP TABLE USERS;
      
      





Los argumentos de consulta SQL pueden crear muchos problemas si no se controlan. Aquí es donde las herramientas de análisis de seguridad pueden ayudar mucho. Le permiten encontrar vulnerabilidades en el código que los desarrolladores introdujeron inadvertidamente.



Confirmar comando



No utilice el comando assert para proteger partes del código base a las que los usuarios no deberían poder acceder. Ejemplo simple:



def foo(request, user): 

      assert user.is_admin, "user does not have access" 

     #      
      
      





De forma predeterminada, __debug__ se establece en True. Sin embargo, hay una serie de optimizaciones que se pueden realizar en producción, incluida la configuración de __debug__ en False. En este caso, las aserciones comandos fallarán y el atacante alcanzarán el código restringido.



Utilice el comando assert solo para enviar mensajes sobre los matices de la implementación a otros desarrolladores.



Bandido



Bandit es una herramienta de código abierto escrita en Python. Ayuda a analizar el código Python y encontrar las vulnerabilidades más comunes en él. Hablé de algunos de ellos en la sección anterior. Con el administrador de paquetes pip , Bandit se puede instalar fácilmente localmente o en una máquina virtual remota, por ejemplo. 



Esta cosa se instala usando un comando simple:



$ pip install bandit
      
      





Bandit ha encontrado aplicaciones en dos áreas principales:



  1. DevSecOps : como uno de los procesos de Integración Continua (CI).
  2. Desarrollo : como parte del kit de herramientas del desarrollador local, se utiliza para probar el código en busca de vulnerabilidades antes de confirmarlo.


Cómo usar Bandit



Bandit se puede integrar fácilmente como parte de las pruebas de CI, y se pueden realizar comprobaciones de vulnerabilidad antes de enviar el código a producción. Por ejemplo, los ingenieros de DevSecOps pueden iniciar Bandit siempre que se produzca una solicitud de extracción o confirmación de código. 



Los resultados de comprobar la vulnerabilidad del código se pueden exportar a CSV, JSON, etc.



Muchas empresas tienen restricciones y prohibiciones sobre el uso de algunos módulos, porque están asociados con ciertas vulnerabilidades bien conocidas en círculos estrechos. Bandit puede mostrar qué módulos se pueden usar y cuáles están en la lista negra: las configuraciones de prueba para las vulnerabilidades correspondientes se almacenan en un archivo especial. Se puede crear usando el Config Generator ( bandit-config-generator):



$ bandit-config-generator -o config.yml
      
      







El archivo config.yml generado contiene bloques de configuración para todas las pruebas y listas negras. Estos datos se pueden eliminar o editar. Para especificar una lista de identificadores de prueba que deben incluirse o excluirse del procedimiento de verificación, utilice los indicadores -t y -s :



  • -t TESTS, --tests TESTS , donde TESTS es una lista de identificadores de prueba (entre corchetes, separados por comas) que se deben incluir.



  •  -s SKIPS, --skip SKIPS, donde SKIPS es una lista de identificadores de prueba (entre corchetes, separados por comas) que se excluirán.


La forma más sencilla es utilizar un archivo de configuración con la configuración predeterminada.



$  bandit -r code/ -f csv -o out.csv

[main]  INFO    profile include tests: None

[main]  INFO    profile exclude tests: None

[main]  INFO    cli include tests: None

[main]  INFO    cli exclude tests: None

[main]  INFO    running on Python 3.8.5

434 [0.. 50.. 100.. 150.. 200.. 250.. 300.. 350.. 400.. ]

[csv]   INFO    CSV output written to file: out.csv

      
      





En el comando anterior, después del indicador -r, se indica el directorio del proyecto, después del indicador -f, el formato de salida, y después del indicador -o, se especifica un archivo en el que se deben escribir los resultados de la verificación. Bandit comprueba todo el código Python dentro del directorio del proyecto y devuelve el resultado en formato CSV. 



Después de verificar, recibiremos mucha información:







Continuación de la tabla



Como se mencionó en la sección anterior, la importación del módulo de subproceso y el uso del argumento shell = True en la llamada a la función subprocess.check_output representan un riesgo de seguridad serio. Si el uso de este módulo y argumento es inevitable, se pueden incluir en la lista blanca en el archivo de configuración y hacer que Bandit omita las pruebas al incluir los ID de B602 (subprocess_popen_with_shell_equals_true) y B404 (import_subprocess) en la lista SKIPS :



$ bandit-config-generator -s [B602, B404] -o config.yml 
      
      





Si volvemos a ejecutar Bandit usando el nuevo archivo de configuración, la salida será un archivo CSV vacío. Esto significa que todas las pruebas han pasado:



> bandit -c code/config.yml -r code/ -f csv -o out2.csv

[main]  INFO    profile include tests: None

[main]  INFO    profile exclude tests: B404,B602

[main]  INFO    cli include tests: None

[main]  INFO    cli exclude tests: None

[main]  INFO    using config: code/config.yml

[main]  INFO    running on Python 3.8.5

434 [0.. 50.. 100.. 150.. 200.. 250.. 300.. 350.. 400.. ]

[csv]   INFO    CSV output written to file: out2.csv
      
      







En un entorno de desarrollo de equipo, para cada proyecto, se deben crear sus propios archivos de configuración. Los desarrolladores deben tener la capacidad de editarlos en cualquier momento, incluso localmente.



¿Qué es más importante para ti?



Este fue un breve tutorial sobre los conceptos básicos para trabajar con Bandit. Si usa módulos en sus proyectos que duda, puede verificarlos en busca de vulnerabilidades ahora mismo. Y a veces no tenemos tiempo para recordar nuestro propio código, sacrificando no solo soluciones hermosas, sino también la seguridad. Cuales son tus prioridades?






Los servidores VPS de Macleod son rápidos y seguros.



Regístrese usando el enlace de arriba o haciendo clic en el banner y obtenga un 10% de descuento durante el primer mes de alquiler de un servidor de cualquier configuración.






All Articles