Aikido

Utilisation des modèles de raisonnement dans AutoTriage

Écrit par
Berg Severens

TL;DR

Les modèles de raisonnement ne sont pas nécessaires pour la plupart des règles SAST, mais pour les cas limites comme le path traversal en JavaScript, ils détectent deux fois plus de faux positifs.

Les modèles de raisonnement : simple effet de mode ?

Les « modèles de raisonnement » connaissent un grand succès. Les grands laboratoires d'IA sont engagés dans une bataille de leadership, repoussant les limites de la taille et des performances des modèles grâce aux lois d'échelle, à un pré-entraînement plus intelligent et à l'ajustement fin avec le RLHF (Reinforcement Learning from Human Feedback). Ils superposent également le « chain-of-thought prompting » pour faire en sorte que les modèles « pensent à voix haute » pendant l'inférence. Cela leur permet de dominer les systèmes non-IA sur les tâches logiques – et de se hisser en tête des classements par la même occasion. 

C'est impressionnant. Mais sont-ils réellement utiles en pratique ? Cela dépend.

Dans le cas d'AutoTriage*, beaucoup dépend de la complexité de la règle SAST**.

*Petit rappel - AutoTriage est une fonctionnalité qu'Aikido utilise pour filtrer les faux positifs des résultats SAST.
**
Un résultat SAST est une vulnérabilité potentielle découverte dans le code source, telle que signalée par un détecteur de motifs codés en dur.

Trop coûteux pour la plupart des règles SAST

AutoTriage fonctionne en deux étapes : nous tentons d'abord d'exclure la possibilité d'exploitabilité. Si cela est possible, nous pouvons filtrer un faux positif. Cela nécessite une approche binaire concernant l'atteignabilité des variables contrôlées par l'utilisateur dans les vulnérabilités. Le modèle vérifie si la variable est réellement contrôlée par l'utilisateur et si une assainissement/validation/conversion de type est en place. Et s'il existe une forme de mitigation, il détermine si elle est réellement efficace.

La deuxième étape n'intervient que lorsque nous ne pouvons pas exclure la possibilité d'exploitabilité lors de la première étape. Nous nous concentrerons alors sur la priorisation. La priorité est définie par la probabilité qu'un problème survienne et la gravité si cela devait se produire. Cette deuxième étape est moins binaire, mais dépend également d'estimations subjectives – par exemple, une variable étant contrôlée par l'utilisateur lorsque le contexte complet nous manque.

Pour la plupart des règles, nous pouvons résumer comment procéder en un nombre raisonnable de « règles empiriques », rendant les modèles de raisonnement superflus : ils ont tendance à avoir une précision similaire, mais ils sont associés à un coût nettement plus élevé.

Pourquoi les petits modèles de raisonnement fonctionnent

Certaines règles sont étonnamment complexes et les modèles non-raisonnants ont du mal à les appréhender. Imaginez utiliser exactement le même espace mental pour chaque mot que vous prononcez : au début, quelqu'un vous demande : « qu'est-ce que 1+1 ? » Puis, cette même personne vous demande : « qu'est-ce que 26248 + 346237 ? » Alors que les modèles normaux peinent avec des niveaux de complexité variables, les modèles de raisonnement peuvent les gérer en utilisant simplement plus de mots pour les entrées complexes et en décomposant les problèmes plus importants en sous-problèmes plus petits et plus gérables.

Malheureusement, comme ils consomment plus de tokens, ils sont généralement aussi plus coûteux. Cependant, les modèles structurés comme des modèles de raisonnement souffrent moins de la réduction de leur taille que les modèles non-raisonnants. Il y a deux raisons d'opter pour des modèles plus grands : (1) ils ont une plus grande capacité à stocker plus de connaissances (mais avoir beaucoup de connaissances n'est pas vraiment nécessaire dans le cas du triage des vulnérabilités). (2) Les modèles plus grands ont tendance à être un peu plus précis par mot. Néanmoins, les modèles de raisonnement peuvent se remettre de leurs erreurs, grâce à leur structure de raisonnement. Ainsi, malgré une consommation de tokens plus élevée, il est faisable en pratique de travailler avec des modèles plus petits ayant un coût par token inférieur pour compenser l'utilisation accrue de tokens.

Path Traversal en Javascript

Le Path Traversal est une règle où les modèles de raisonnement peuvent vraiment exceller, car ils sont étonnamment complexes à trier. Le Path Traversal est une vulnérabilité où les utilisateurs finaux pourraient lire ou écrire des fichiers en dehors d'un répertoire prévu. Par exemple, imaginez que Google Drive ait un dossier dédié à chaque utilisateur séparément sur un système de fichiers comme ceci :

Google Drive/userId1/
Google Drive/userId2/…

La prochaine fois que vous voudrez télécharger l'un de vos fichiers, vous enverrez une requête GET de votre client de navigateur à Google Drive, par exemple avec le nom de fichier myDogEatingShoes.jpg. Si ce fichier existe, votre téléchargement commencera rapidement. Mais que se passerait-il si vous essayiez le nom de fichier suivant : ../userId2/mypasswords.txt. Si Google Drive n'avait pas protégé son back-end contre le Path Traversal, vous pourriez alors télécharger un fichier ‘mypasswords.txt’ d'un autre utilisateur, si ce fichier existe.

Différentes attaques par Path Traversal

Pour trier les résultats SAST de Path Traversal, nous devons comprendre les différents cas où quelque chose est vulnérable ou non. Commençons par les cas simples et augmentons progressivement la complexité.

Modèle 1 : ‘../’

Le problème majeur ici est le motif ‘../’. Si vous lisez ou écrivez dans un chemin de fichier contenant ‘../’, cela pourrait sortir du répertoire prévu et lire/écrire là où vous ne l'aviez pas l'intention. Ainsi, s'il n'y a pas de vérification de ‘../’ dans le chemin de fichier et que le fichier est spécifié côté client, vous avez une réelle vulnérabilité. Dans les pires cas, les attaquants pourraient lire des fichiers contenant des identifiants sur votre système.

Modèle 2 : ‘..\\’

Imaginez que vous ayez vérifié ‘../’, mais que le code s'exécute sur un système Windows. Vous auriez à nouveau un problème, car le Path Traversal est toujours possible avec les motifs ‘..\\’. Jusqu'ici tout va bien, deux règles de base à vérifier sont encore gérables, n'est-ce pas ?

Modèle 3 : ‘..’

Afin d'obtenir des chemins propres et sans barres obliques manquantes, beaucoup de gens utilisent des fonctions comme path.resolve() ou path.join(). C'est là que les choses se compliquent. Imaginez quelque chose comme ceci :

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);‍

Il s'avère que c'est toujours vulnérable : si un attaquant utilise userControlledSubPath === '..', le path.join l'interprétera toujours comme remontant d'un répertoire.

Cependant, le dernier argument dans path.join() est immunisé contre cette attaque. Si un attaquant spécifiait le « .. » dans le dernier argument, la path.join() fonction renverrait un répertoire au lieu d'un chemin de fichier, ce qui entraînerait une opération de lecture/écriture invalide.

Modèle 4 : « /* »

Dans un nouvel exemple, nous avions à nouveau un test comme celui-ci :

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

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

return fs.readFileSync(filepath);

Cela semble sûr, n'est-ce pas ? La vérification couvre les cas « .. », « ../ » et « ..\\ » – c'est élégant ! Voici maintenant la manière surprenante dont cela reste vulnérable. Roulement de tambour… trrrrrrrrr… Lorsqu'un argument dans path.resolve() commence par une barre oblique, il ignore tous les arguments précédents. Ainsi, lorsqu'un attaquant fait quelque chose comme filename = /etc/passwd, alors path.resolve() ignorera le chemin de base codé en dur et se résoudra en /etc/passwd. Effrayant, n'est-ce pas ? Nous aurions dû vérifier également cette barre oblique finale. Notez que l'utilisation de path.join() l'aurait rendu sûr.

Apprécier la complexité

Charlie Chaplin a dit un jour : « La simplicité n'est pas une chose simple. » Cela s'applique ici aussi : des remédiations simples et efficaces existent, mais il faut d'abord comprendre l'éventail des vecteurs d'attaque possibles. La remédiation la plus simple et la plus robuste contre le path traversal consiste à résoudre d'abord le chemin et à vérifier s'il commence toujours par le chemin de base prévu. Il n'y a aucun moyen d'échapper à cette vérification.

Cependant, l'équipe AutoTriage n'a pas le luxe de pouvoir choisir la remédiation. Nous devons pouvoir marquer les solutions alternatives comme sûres afin de ne pas submerger inutilement les clients avec de faux positifs. Nous avons maintenant vu 4 vecteurs d'attaque différents de path traversal et ils sont tous accompagnés de vérifications spécifiques. Pour chacun de ces vecteurs d'attaque, le LLM doit vérifier s'il peut potentiellement se produire avec toutes les exigences pour soit réussir une attaque, soit exclure toute possibilité d'attaque.

Malgré le fait que les modèles de raisonnement ne sont pas la norme pour la plupart des règles, ils sont capables de filtrer en toute sécurité deux fois plus de faux positifs que les modèles non-raisonnement pour le path traversal en JavaScript. C'est un véritable atout pour la réduction du bruit.

Partager :

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

Abonnez-vous pour les actualités sur les menaces.

Commencez dès aujourd'hui, gratuitement.

Commencer gratuitement
Sans carte bancaire

Sécurisez votre environnement dès maintenant.

Sécurisez votre code, votre cloud et votre environnement d’exécution dans un système centralisé unique.
Détectez et corrigez les vulnérabilités rapidement et automatiquement.

Aucune carte de crédit requise | Résultats en 32 secondes.