Aikido

Uso de modelos de razonamiento en AutoTriage

Escrito por
Berg Severens

TL;DR

Los modelos de razonamiento no son necesarios para la mayoría de las reglas SAST, pero para casos límite como el path traversal en JavaScript, detectan el doble de falsos positivos.

¿Son los modelos de razonamiento solo una moda?

Los "modelos de razonamiento" están en auge. Los grandes laboratorios de IA están inmersos en una batalla por el liderazgo, empujando los límites del tamaño y rendimiento de los modelos a través de leyes de escalado, preentrenamiento más inteligente y ajuste fino con RLHF (Aprendizaje por Refuerzo a partir de Retroalimentación Humana). También están aplicando el "chain-of-thought prompting" para que los modelos "piensen en voz alta" durante la inferencia. Esto les permite dominar sobre los sistemas no basados en IA en tareas de lógica, y encabezar las clasificaciones mientras lo hacen. 

Eso es impresionante. Pero, ¿son realmente útiles en la práctica? Depende.

En el caso de AutoTriage*, mucho depende de la complejidad de la regla SAST**.

*Recordatorio rápido - AutoTriage es una característica que Aikido utiliza para filtrar los falsos positivos de SAST.
**
Un hallazgo SAST es una vulnerabilidad potencial descubierta en el código fuente, según lo reportado por un detector de patrones codificados.

Demasiado costoso para la mayoría de las reglas SAST

AutoTriage funciona en dos pasos: primero intentamos descartar la posibilidad de explotabilidad. Si eso es posible, podemos filtrar un falso positivo. Esto requiere un pensamiento binario sobre la accesibilidad de las variables controladas por el usuario a las vulnerabilidades. El modelo básicamente verifica si la variable está realmente controlada por el usuario y si hay sanitización/validación/casting implementado. Y si existe alguna forma de mitigación, determina si es realmente efectiva.

El segundo paso solo ocurre cuando no podemos descartar la posibilidad de explotabilidad en el primer paso. Entonces nos centraremos en la priorización. La prioridad se define por la probabilidad de que algo salga mal y la gravedad si ocurriera. Este segundo paso es menos binario, pero también depende de estimaciones subjetivas, por ejemplo, que una variable sea controlada por el usuario cuando carecemos de contexto completo.

Para la mayoría de las reglas, podemos resumir cómo hacer esto en un número razonable de "reglas generales", lo que hace que los modelos de razonamiento sean excesivos: tienden a tener una precisión similar, pero con un coste significativamente mayor.

Por qué funcionan los modelos de razonamiento pequeños

Algunas reglas son sorprendentemente complejas y los modelos sin capacidad de razonamiento tienen dificultades para comprenderlas. Imagina usar exactamente el mismo espacio mental para cada palabra que dices: inicialmente alguien te pregunta: “¿cuánto es 1+1?” Luego, esa misma persona te pregunta “¿cuánto es 26248 + 346237?” Mientras que los modelos normales luchan con niveles de complejidad variables, los modelos de razonamiento pueden manejarlos simplemente usando más palabras para entradas complejas y descomponiendo problemas más grandes en subproblemas más pequeños y manejables.

Desafortunadamente, debido a que consumen más tokens, generalmente también son más caros. Sin embargo, los modelos estructurados como modelos de razonamiento sufren menos por la reducción del tamaño del modelo que los modelos sin capacidad de razonamiento. Hay dos razones para optar por modelos más grandes: (1) tienen más capacidad para almacenar más conocimiento (pero tener mucho conocimiento no es realmente necesario en el caso de clasificar vulnerabilidades). (2) Los modelos más grandes tienden a ser un poco más precisos por palabra. Sin embargo, los modelos de razonamiento pueden recuperarse de los errores, gracias a su estructura de razonamiento. Así que, a pesar de consumir más tokens, es factible en la práctica trabajar con modelos más pequeños con un coste por token más bajo para compensar el mayor uso de tokens.

Path Traversal en Javascript

El path traversal es una regla donde los modelos de razonamiento pueden destacar, porque son sorprendentemente complejos de clasificar. El path traversal es una vulnerabilidad donde los usuarios finales podrían leer o escribir archivos fuera de un directorio previsto. Por ejemplo, imagina que Google Drive tuviera una carpeta dedicada a cada usuario por separado en un sistema de archivos como este:

Google Drive/userId1/
Google Drive/userId2/…

La próxima vez que quieras descargar uno de tus archivos, envías una solicitud GET desde tu cliente de navegador a Google Drive, por ejemplo, con el nombre de archivo myDogEatingShoes.jpg. Si ese archivo existe, tu descarga comenzará de inmediato. Pero, ¿qué pasaría si intentaras el siguiente nombre de archivo: ../userId2/mypasswords.txt. Si Google Drive no hubiera protegido su back-end contra el path traversal, entonces podrías descargar un archivo ‘mypasswords.txt’ de otro usuario, si ese archivo existe.

Diferentes ataques de Path Traversal

Para clasificar los hallazgos de SAST de path traversal, necesitamos entender los diferentes casos en los que algo es vulnerable o no. Comencemos con los casos sencillos y aumentemos gradualmente la complejidad.

Patrón 1: ‘../’

El problema principal aquí es el patrón ‘../’. Si lees o escribes en una ruta de archivo que contenga ‘../’, podría escapar del directorio previsto y leer/escribir en un lugar no deseado. Así que, si no hay una comprobación de ‘../’ en la ruta del archivo y este se especifica del lado del cliente, tienes una vulnerabilidad real. En los peores casos, los atacantes podrían leer archivos que contengan credenciales en tu sistema.

Patrón 2: ‘..\\’

Imagina que comprobaste ‘../’, pero el código se está ejecutando en un sistema Windows. Tendrías un problema de nuevo, ya que el path traversal sigue siendo posible con patrones ‘..\\’. Hasta ahora todo bien, dos reglas generales a comprobar siguen siendo manejables, ¿verdad?

Patrón 3: ‘..’

Para obtener rutas limpias y sin barras faltantes, mucha gente utiliza funciones como path.resolve() o path.join(). Aquí es donde empieza lo interesante. Imagina algo como esto:

if (userControlledSubPath.includes(‘../’) || userControlledSubPath.includes(‘..\\’)|| filename.includes(‘../’) || filename.includes(‘..\\’)) 
{	
   throw new Error(‘Path traversal attempt detected);
}‍

const filepath = path.join(HARDCODED_BASE_PATH, userControlledSubPath, filename);

return fs.readFileSync(filepath);‍

Resulta que esto sigue siendo vulnerable: si un atacante utiliza userControlledSubPath === '..', el path.join lo seguirá interpretando como si subiera un directorio.

Sin embargo, el último argumento en path.join() es inmune a ese ataque. Si un atacante especificara '..' en el último argumento, la path.join() función devolvería un directorio en lugar de una ruta de archivo, lo que resultaría en una operación de lectura/escritura no válida.

Patrón 4: '/*'

En un nuevo ejemplo, tuvimos de nuevo una prueba como esta:

if (filename.includes(‘..’)) 
{	
    throw new Error(‘Path traversal attempt detected);
}

const filepath = path.resolve(HARDCODED_BASE_PATH, filename);

return fs.readFileSync(filepath);

¿Esto parece seguro, verdad? La comprobación cubre los casos '..', '..//' y '..\\' - ¡es elegante! Ahora viene la sorprendente forma en que esto sigue siendo vulnerable. Redoble de tambores... trrrrrrrrr... Cuando un argumento en path.resolve() comienza con una barra, ignora todos los argumentos anteriores. Así que cuando un atacante hace algo como filename = /etc/passwd, entonces path.resolve() ignorará la ruta base codificada y se resolverá a /etc/passwd. Da miedo, ¿verdad? Deberíamos haber comprobado también esa barra final. Ten en cuenta que usar path.join() lo habría hecho seguro.

Apreciando la complejidad

Charlie Chaplin dijo una vez: 'La simplicidad no es algo simple'. Esto también se aplica aquí: existe una remediación simple y efectiva, pero primero es necesario comprender el rango de posibles vectores de ataque. La remediación más simple y robusta contra la path traversal es primero resolver la ruta y verificar si aún comienza con la ruta base prevista. No hay forma de eludir esa comprobación.

Sin embargo, el equipo de AutoTriage no tiene el lujo de poder elegir la remediación. Necesitamos poder marcar soluciones alternativas como seguras para no abrumar innecesariamente a los clientes con falsos positivos. Hemos visto ya 4 vectores de ataque diferentes de path traversal y todos vienen con comprobaciones específicas. Para cada uno de estos vectores de ataque, el LLM necesita verificar si puede ocurrir con todos los requisitos para realizar un ataque exitoso o para descartar cualquier posibilidad de ataque.

A pesar de que los modelos de razonamiento no son los predeterminados para la mayoría de las reglas, son capaces de filtrar de forma segura el doble de falsos positivos en comparación con los modelos sin razonamiento para path traversal en JavaScript. Esto cambia las reglas del juego para la reducción de ruido.

Compartir:

https://www.aikido.dev/blog/reasoning-models-autotriage

Suscríbase para recibir noticias sobre amenazas.

Empieza hoy mismo, gratis.

Empieza gratis
Sin tarjeta

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.