Auditar la seguridad del código Python en Debian 9

Intro

Una buena práctica de seguridad en programación es realizar auditorías periódicas del código a medida que se va escribiendo, de tal forma que las correcciones que debamos aplicar sean mucho menores y más fáciles de corregir que si lo realizamos al final del desarrollo.

Para Python existen una serie de herramientas para poder hacer estas auditorías, las cuales vamos a mostrar a continuación.

Estas pruebas las hemos realizado en un sistema operativo Debian 9 y para Python 3 en entornos virtuales virtualenv.


Herramientas

Vamos a utilizar las siguientes herramientas:

  • Auditoría del código:
    • Bandit para Python3.
    • MyPy.
  • Auditoría de dependencias:
    • Safety.

Entorno virtual

Para realizar la auditoría del código con las herramientas indicadas en el apartado anterior vamos a crear un entorno virtual en una Raspberry:

root@raspberrypi:/usr/local/scripts# mkdir audit
root@raspberrypi:/usr/local/scripts# cd audit/
root@raspberrypi:/usr/local/scripts/audit# python3 -m virtualenv .audit

Y lo activamos:

root@raspberrypi:/usr/local/scripts/audit# . .audit/bin/activate
(.audit) root@raspberrypi:/usr/local/scripts/audit#

Para más información sobre entornos virtuales puedes consultar esta entrada del blog sobre entornos virtuales en Python3.


Código para auditar

Vamos a utilizar un archivo que llamaremos audit.py con el siguiente código Python:

import sys
import os
from urllib import request

url = request.urlopen(sys.argv[1])
print(url)

value = os.popen('uname -a')
for i in value.__iter__():
 print(i)

num = 5
print('The value is ' + num)

Este contiene varios errores (str + int) o comandos poco seguros (Shell command), de los cuales seremos advertidos usando los programas que vamos a analizar.


Auditoría del código

Para realizar la auditoría del código escrito vamos a ver dos herramientas muy interesantes, ambas con licencia de software libre.

Bandit

Este programa audita la seguridad del código, pero no los errores de programación. Se encuentra bajo el repositorio oficial de Debian 9, en donde podemos encontrar distintas versiones, tal y como se muestra a continuación.

Instalación

root@raspberrypi:/home/sergio# apt-cache search bandit
bandit - Security oriented static analyzer for Python code - Metapackage
python-bandit - Security oriented static analyzer for Python code - Python 2.7
python3-bandit - Security oriented static analyzer for Python code - Python 3.x

Si instalamos el primero de los programas nos indica que está escrito para Python 2.7.x:

(.audit) root@raspberrypi:/usr/local/scripts/audit# bandit --version
bandit 1.5.1
  python version = 2.7.16 (default, Oct 10 2019, 22:02:15) [GCC 8.3.0]

Por lo que vamos a instalar el que aparece para Python 3:

(.audit) root@raspberrypi:/usr/local/scripts/audit# apt-get install python3-bandit

Y comprobamos que la versión es la correcta para auditar el código de Python 3:

(.audit) root@raspberrypi:/usr/local/scripts/audit# python3-bandit --version
python3-bandit 1.5.1
  python version = 3.7.3 (default, Dec 20 2019, 18:57:59) [GCC 8.3.0]

Uso

Si auditamos el código que usamos como ejemplo, archivo llamado audit.py, con el siguiente comando:

(.audit) root@raspberrypi:/usr/local/scripts/audit# python3-bandit audit.py

Obtenemos los siguientes resultados:

Test results:
>> Issue: [B310:blacklist] Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected.
   Severity: Medium   Confidence: High
   Location: audit.py:5
   More Info: https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b310-urllib-urlopen
4
5       url = request.urlopen(sys.argv[1].read())
6       print(url)

--------------------------------------------------
>> Issue: [B605:start_process_with_a_shell] Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell
   Severity: Low   Confidence: High
   Location: audit.py:8
   More Info: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html
7
8       value = os.popen('uname -a')
9       for i in value.__iter__():

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   Location: audit.py:8
   More Info: https://bandit.readthedocs.io/en/latest/plugins/b607_start_process_with_partial_path.html
7
8       value = os.popen('uname -a')
9       for i in value.__iter__():

--------------------------------------------------

Code scanned:
        Total lines of code: 10
        Total lines skipped (#nosec): 0

Run metrics:
        Total issues (by severity):
                Undefined: 0.0
                Low: 2.0
                Medium: 1.0
                High: 0.0
        Total issues (by confidence):
                Undefined: 0.0
                Low: 0.0
                Medium: 0.0
                High: 3.0
Files skipped (0):

Nos muestra cada advertencia con una valoración de severidad y confiabilidad, junto a un enlace en donde obtener más información al respecto sobre esa advertencia en concreto.

El programa tiene una gran cantidad de opciones, las cuales te recomiendo probar consultando la ayuda contextual ejecutando el comando:

(.audit) root@raspberrypi:/usr/local/scripts/audit# python3-bandit --help

Como comentamos al comienzo de esta sección, este programa no detecta errores de escritura en la estructura del código, como por ejemplo el error al concatenar una variable numérica con un texto. Para cubrir esta carencia vamos a ver la siguiente herramienta.

MyPy

Este es un programa verificador de tipo estátito para Python, el cual nos mostrará posibles errores en la escritura de nuestro código sin necesidad de ejecutarlo.

Instalación

Para instalarlo en nuestro entorno virtual ejecutaremos el siguiente comando:

(.audit) root@raspberrypi:/usr/local/scripts/audit# python -m pip install mypy -U

Uso

Su uso es muy sencillo, tal y como veremos a continuación ejecutando el siguiente comando:

(.audit) root@raspberrypi:/usr/local/scripts/audit# mypy audit.py

Obtendremos el siguiente resultado:

audit.py:13: error: Unsupported operand types for + ("str" and "int")
Found 1 error in 1 file (checked 1 source file)

Nos muestra el error de sintaxys que hemos introducido, como es el concatenar un texto con una variable de tipo numérico (int).

El programa tiene una gran cantidad de opciones, las cuales te recomiendo probar consultando la ayuda contextual ejecutando el comando:

(.audit) root@raspberrypi:/usr/local/scripts/audit# mypy --help

Auditoría de dependencias

Para realizar la auditoría de las dependencias que pueda afectar a nuestro código, esto quiere decir los paquetes instalados en nuestro sistema o entorno virtual, haremos uso de una herramienta en su versión gratuita.

Safety

Esta herramienta es de pago, pero tiene una versión gratuita para uso no comercial. Nos mostrará cualquier paquete desactualizado que tengamos instalado, con el consiguiente riesgo de seguridad que ello pudiera suponer.

Instalación

Para instalarlo usaremos el siguiente comando:

(.audit) root@raspberrypi:/usr/local/scripts/audit# python -m pip install safety

Uso

Podemos chequear los paquetes instalados en nuestro sistema o entorno virtual, que es nuestro caso, o podemos chequear los paquetes especificados en el archivo requirementes.txt que se suele usar para instalar en un único comando con pip.

Instalemos un paquete antiguo para ver el resultado del test:

(.audit) root@raspberrypi:/usr/local/scripts/audit# python -m pip install httplib2==0.15.0

Ahora si ejecutamos el siguiente comando:

(.audit) root@raspberrypi:/usr/local/scripts/audit# safety check

Obtenemos el siguiente resultado:

+==============================================================================+
|                                                                              |
|                               /$$$$$$            /$$                         |
|                              /$$__  $$          | $$                         |
|           /$$$$$$$  /$$$$$$ | $$  \__//$$$$$$  /$$$$$$   /$$   /$$           |
|          /$$_____/ |____  $$| $$$$   /$$__  $$|_  $$_/  | $$  | $$           |
|         |  $$$$$$   /$$$$$$$| $$_/  | $$$$$$$$  | $$    | $$  | $$           |
|          \____  $$ /$$__  $$| $$    | $$_____/  | $$ /$$| $$  | $$           |
|          /$$$$$$$/|  $$$$$$$| $$    |  $$$$$$$  |  $$$$/|  $$$$$$$           |
|         |_______/  \_______/|__/     \_______/   \___/   \____  $$           |
|                                                          /$$  | $$           |
|                                                         |  $$$$$$/           |
|  by pyup.io                                              \______/            |
|                                                                              |
+==============================================================================+
| REPORT                                                                       |
| checked 39 packages, using default DB                                        |
+============================+===========+==========================+==========+
| package                    | installed | affected                 | ID       |
+============================+===========+==========================+==========+
| httplib2                   | 0.15.0    | <0.18.0                  | 38303    |
+==============================================================================+

Nos muestra el paquete que tenemos desactualizado en nuestro entorno virtual, el cual si actualizamos nos dejaría de mostrar como advertencia:

(.audit) root@raspberrypi:/usr/local/scripts/audit# python -m pip install httplib2 --upgrade
...
(.audit) root@raspberrypi:/usr/local/scripts/audit# safety check
...
| REPORT                                                                       |
| checked 39 packages, using default DB                                        |
+==============================================================================+
| No known security vulnerabilities found.                                     |
+==============================================================================+

Si el chequeo lo hacemos sobre el archivo requeriments.txt, el cual contiene el siguiente listado de paquetes:

httplib2==0.15.0

Podemos ejecutar el siguiente comando sobre el fichero:

(.audit) root@raspberrypi:/usr/local/scripts/audit# safety check -r requeriments.txt

Obteniendo el mismo resultado que antes, solo que en lugar de indicar que se chequearon 39 paquetes instalados en el sistema indica que se chequeó 1 paquete incluido en el archivo.


Conclusión

Chequear la seguridad e integridad del código durante el desarrollo es buena práctica, pudiendo reducir la complejidad y coste de realizarlo al final del desarrollo o cuando surja algún problema una vez sacado a producción. Estas herramientas nos pueden ayudar en esta tarea.


Enlaces de interés

Enlace oficial de Bandig

Enlace oficial en GitHub de MyPy

Página oficial de Safety