cd ~/blog

~/vulns/python-library-hijacking.md

Python Library Hijacking

privescautor: Jordy Pérez Osorio aviso legal
LinuxPythonprivesclibrary-hijacking

~7 min de lectura


1. Descripción técnica del ataque

Python Library Hijacking es el abuso del mecanismo de importación de Python para conseguir que un proceso cargue código controlado por el atacante en lugar del módulo esperado, o para alterar directamente el módulo que el proceso cargará. El cruce de confianza aparece cuando un usuario con menos privilegios controla el contenido de un módulo que después importa un proceso ejecutado con otra identidad, por ejemplo mediante sudo, una tarea programada o un servicio.

Una instrucción import no se limita a leer definiciones. Si el módulo todavía no está almacenado en sys.modules, el sistema de importación lo localiza, crea el objeto de módulo y ejecuta su código de nivel superior durante la carga. Por eso, controlar el archivo seleccionado permite ejecutar instrucciones con la identidad del intérprete que realiza la importación.

1.1 Resolución y ejecución del módulo

El buscador basado en rutas consulta las entradas de sys.path en orden. La primera entrada depende de cómo se inició Python: al ejecutar un script suele ser el directorio que contiene ese script, mientras que con -c, -m o una sesión interactiva puede ser el directorio actual. Después se incorporan otras ubicaciones, como PYTHONPATH, la biblioteca estándar y site-packages.

El ataque no se confirma sólo porque un script contenga import modulo. También debe demostrarse que el atacante puede crear o modificar el candidato que Python seleccionará y que una nueva carga ejecutará ese contenido bajo la identidad privilegiada.

// renderizando diagrama…

1.2 Variantes principales

Sombreado de módulo: el atacante crea un archivo con el mismo nombre que una dependencia legítima en un directorio consultado antes que la ubicación real. El resultado depende del orden de sys.path, no sólo de que el directorio de trabajo sea escribible.

Modificación del módulo resuelto: el atacante tiene permisos de escritura sobre el archivo que la importación legítima ya selecciona. No necesita ganar prioridad en la búsqueda porque modifica directamente el candidato que el proceso utilizará.

Manipulación de la ruta de búsqueda: variables de entorno, archivos .pth, paquetes o configuraciones que introducen una ruta controlada pueden producir el mismo efecto. Esta variante debe demostrarse inspeccionando el entorno real del proceso privilegiado, ya que herramientas como sudo pueden filtrar variables.

Esta técnica no equivale a PATH Hijacking. PATH Hijacking altera la resolución de ejecutables realizada por el sistema o por un shell. Python Library Hijacking altera la resolución o el contenido de módulos importados por el intérprete. Tampoco equivale a deserialización insegura de Pickle: que el archivo malicioso se llame pickle.py no implica que se haya procesado un objeto serializado.

2. Impacto

El impacto depende de la identidad y del contexto del proceso importador. Si un script autorizado por sudo carga el módulo controlado, el código de nivel superior puede ejecutarse como el usuario permitido o como root. En un servicio, puede otorgar las capacidades de su cuenta de ejecución y acceso a sus secretos, archivos o sockets.

La escritura sobre una biblioteca compartida también puede afectar procesos distintos del objetivo y romper aplicaciones legítimas. Por ello, una prueba debe limitarse al entorno autorizado, preservar el archivo original cuando corresponda y evitar presentar el permiso de escritura como evidencia de ejecución hasta observar la carga privilegiada.

3. Condiciones necesarias

Para confirmar una ruta de explotación deben coincidir estas condiciones:

  1. Existe un proceso que importa un módulo de Python con una identidad o capacidades que el atacante no posee.
  2. El atacante controla un archivo que puede ser seleccionado para ese nombre de módulo, o puede modificar directamente el archivo seleccionado.
  3. La ruta controlada tiene prioridad suficiente o se ha verificado que el archivo modificable es el resuelto por el intérprete.
  4. El proceso realiza una carga que ejecuta el contenido alterado. Un módulo ya presente en sys.modules puede evitar una nueva búsqueda dentro del mismo intérprete.
  5. Las restricciones del lanzador, del entorno y del sistema de archivos no eliminan la ruta controlada ni impiden leer el módulo.

Un directorio escribible junto al script es un indicio fuerte para el sombreado, pero no sustituye la inspección de sys.path. Del mismo modo, encontrar un módulo escribible no demuestra que el proceso privilegiado lo importe.

4. Validación y detección

Primero identifica el comando exacto, la identidad con que se ejecuta y los imports alcanzados por esa ruta. Después consulta la resolución usando la misma versión de Python y, cuando sea posible, el mismo entorno del proceso objetivo.

Ejemplos de comprobaciones generales:

python3 -c 'import sys; print("\n".join(sys.path))'
python3 -c 'import modulo; print(modulo.__file__)'
namei -l /ruta/al/modulo.py
find /ruta/de/python -type f -writable -ls

modulo.__file__ permite saber qué archivo se resolvió en esa ejecución. Debe interpretarse con cuidado: cambiar el directorio, el usuario, las variables o la forma de invocar Python puede producir otra ruta. namei -l ayuda a revisar los permisos de cada directorio del recorrido, mientras que find sólo enumera candidatos y no demuestra que sean importados.

Para detección defensiva conviene revisar:

  • módulos con propietario o permisos incompatibles con la cuenta que ejecuta el servicio;
  • archivos con nombres de biblioteca estándar creados en directorios de aplicaciones;
  • cambios de integridad en bibliotecas del sistema o entornos virtuales;
  • reglas de sudo, servicios y tareas programadas que ejecuten Python desde ubicaciones escribibles;
  • valores inesperados de PYTHONPATH, archivos .pth y rutas añadidas mediante código.

5. Explotación documentada en los writeups

5.1 004-flower

El script autorizado importa pickle y se encuentra en un directorio escribible. La explotación crea un pickle.py local, por lo que la variante es sombreado de módulo. El código se ejecuta durante el import como el usuario rose. El nombre del módulo no convierte el ataque en deserialización de Pickle.

5.2 020-vinylizer

El writeup consulta random.__file__ y confirma que Python resuelve /usr/lib/python3.10/random.py. Como ese archivo tiene permisos 777, la escalada modifica el módulo seleccionado y después ejecuta el script permitido mediante sudo. Esta variante no depende de colocar otro random.py antes en sys.path.

5.3 061-system y 062-jabita

Ambos casos modifican directamente un módulo importado por un proceso de mayor privilegio. En System el archivo afectado es os.py; en Jabita es wild.py. La debilidad decisiva es que una cuenta sin privilegios puede alterar código que luego consume una ejecución privilegiada.

5.4 127-newbee

El ataque crea un random.py en el mismo directorio que el script ejecutable como debian. Al tener prioridad sobre la biblioteca esperada en esa invocación, el módulo local ejecuta código con la identidad de debian. Es la misma variante de sombreado observada en Flower, aunque cambia el módulo y el usuario objetivo.

6. Mitigación

  • Mantener scripts, paquetes, bibliotecas y todos sus directorios bajo propiedad de una cuenta administrativa, sin escritura para usuarios menos privilegiados.
  • No ejecutar scripts privilegiados desde directorios controlables por el usuario que los invoca.
  • Evitar rutas controladas por usuarios en PYTHONPATH, archivos .pth o modificaciones dinámicas de sys.path.
  • Usar entornos virtuales con propiedad y permisos coherentes con la identidad del servicio.
  • Reducir las reglas de sudo al mínimo y revisar también las dependencias importadas por el comando permitido.
  • Monitorizar integridad y cambios de permisos en bibliotecas del sistema, entornos virtuales y directorios de aplicaciones.
  • Restaurar módulos modificados y rotar secretos accesibles al proceso si se confirma ejecución no autorizada.

Fijar el nombre absoluto del script no resuelve por sí solo el problema. La cadena de confianza incluye todos los módulos y directorios que el intérprete puede cargar durante esa ejecución.

7. Clasificación

El resultado habitual es ejecución de código y, cuando el importador tiene más privilegios, escalada local de privilegios.

8. Referencias y conexiones

// relacionados