Les outils d'intelligence artificielle tels que Bard, ChatGPT et Bing Chat sont les grands noms actuels de la catégorie Large Language Model (LLM) qui est en plein essor.
Les LLM sont formés sur de vastes ensembles de données pour pouvoir communiquer en utilisant le langage humain quotidien comme invite de discussion. Compte tenu de la flexibilité et du potentiel des LLM, les entreprises les intègrent dans de nombreux flux de travail au sein de l'industrie technologique pour rendre notre vie meilleure et plus facile. L'une des principales intégrations de flux de travail IA est un plugin de code à saisie semi-automatique, offrant la possibilité de prévoir et de générer des modèles de code correspondants afin de coder plus rapidement et plus efficacement.
Sur le marché actuel des outils de génération de code d'IA, il existe de nombreux acteurs, notamment GitHub Copilot , Amazon CodeWhisperer , Google Cloud Code (Duet AI) , Blackbox AI Code Generation , et bien d'autres encore. Cet article de blog présente des exemples de pièges de sécurité courants auxquels les développeurs sont confrontés lorsqu'ils utilisent de tels outils.
L'objectif de cet article est de sensibiliser et de souligner que le code généré automatiquement ne peut pas être aveuglément fiable et nécessite toujours un examen de sécurité pour éviter d'introduire des vulnérabilités logicielles.
Remarque : certains fournisseurs suggèrent que leurs outils de saisie semi-automatique peuvent contenir des extraits de code non sécurisés.
De nombreux articles ont déjà souligné que les outils de saisie semi-automatique génèrent des vulnérabilités connues telles que IDOR , l'injection SQL et XSS. Cet article mettra en évidence d'autres types de vulnérabilités qui dépendent davantage du contexte du code, où il existe une forte probabilité que la saisie semi-automatique de l'IA glisse des bogues dans votre code.
Outils de sécurité et de génération de code d'IA Les modèles d'IA formés au code sont généralement formés sur de grandes bases de code avec pour mission principale de produire du code fonctionnel.
De nombreux problèmes de sécurité ont une méthode d'atténuation connue et définie sans compromettre les performances (par exemple, les injections SQL pourraient être évitées en ne concaténant pas les entrées/paramètres utilisateur directement dans la requête) - et peuvent donc être éradiqués car il n'y a aucune raison d'écrire le code de manière non sécurisée.
Cependant, de nombreux problèmes de sécurité dépendent du contexte et pourraient être parfaitement sûrs lorsqu'ils sont implémentés en tant que fonctionnalité interne, tandis que lorsqu'ils sont confrontés à une entrée externe d'un client, ils deviennent une vulnérabilité et sont donc difficiles à distinguer à travers l'ensemble de données d'apprentissage de l'IA.
Dans de telles vulnérabilités, l'utilisation spécifique du code dépend généralement d'autres parties du code et de l'objectif général de l'application.
Voici quelques cas d’utilisation courants et exemples que nous avons testés, illustrant certains de ces types de vulnérabilités :
Après avoir vu comment les outils de génération de code d'IA gèrent les cas ci-dessus, nous avons essayé de tester leur capacité à créer des filtres de sécurité appropriés et d'autres mécanismes d'atténuation.
Voici quelques cas d’utilisation courants et exemples que nous avons testés en demandant délibérément un code sécurisé :
De nombreuses applications incluent du code qui récupère un fichier à l'utilisateur en fonction d'un nom de fichier. L’utilisation de la traversée de répertoires pour lire des fichiers arbitraires sur le serveur est une méthode courante utilisée par les mauvais acteurs pour obtenir des informations non autorisées.
Il peut sembler évident de nettoyer les entrées de nom de fichier/chemin de fichier provenant de l'utilisateur avant de récupérer le fichier, mais nous supposons que c'est une tâche difficile pour un modèle d'IA formé sur des bases de code génériques - en effet, certains ensembles de données n'ont pas besoin de le faire. utiliser des chemins nettoyés dans le cadre de leurs fonctionnalités (cela peut même dégrader les performances ou nuire à la fonctionnalité) ou ne sont pas endommagés par un chemin non nettoyé car ils sont destinés à un usage interne uniquement.
Suggestion d'outil AI (Google Cloud Code - Duet AI) : essayons de générer une fonction de récupération de fichiers de base à partir de l'entrée de l'utilisateur, à l'aide de la saisie semi-automatique de Google Cloud Code - Duet AI. Nous laisserons Duet AI compléter automatiquement notre code, en fonction de nos noms de fonctions et d'itinéraires -
Comme nous pouvons le voir en gris, la suggestion de saisie semi-automatique consiste à utiliser la fonction « send_file » de Flask, qui est la fonction de base de gestion de fichiers de Flask.
Mais même la documentation de Flask indique « Ne jamais transmettre les chemins de fichiers fournis par un utilisateur », ce qui signifie qu'en insérant une entrée utilisateur directement dans la fonction « send_file », l'utilisateur aura la possibilité de lire n'importe quel fichier sur le système de fichiers, par exemple en utilisant des caractères de traversée de répertoire. tel que « ../ » dans le nom du fichier.
Mise en œuvre correcte :
Le remplacement de la fonction « send_file » par la fonction sécurisée « send_from_directory » de Flask atténuera le risque de traversée du répertoire. Il permettra uniquement de récupérer des fichiers à partir d'un répertoire spécifique codé en dur (« exportations » dans ce cas).
Dans certains langages de programmation, tels que PHP et NodeJS, il est possible de faire des comparaisons entre deux types de variables différents et le programme effectuera une conversion de type en coulisse pour terminer l'action.
Les comparaisons lâches ne vérifient pas le type de la variable en coulisse et sont donc plus sujettes aux attaques de jonglerie de types. Cependant, l’utilisation de comparaisons lâches n’est pas considérée en soi comme une pratique de codage non sécurisée : cela dépend du contexte du code. Et encore une fois, il est difficile pour les modèles d’IA de distinguer les cas.
Suggestion d'outil d'IA (Amazon CodeWhisperer) : L'exemple le plus courant de jonglerie de types PHP est celui des comparaisons lâches de jetons/hachages secrets où l'entrée utilisateur lue via une requête JSON (qui permet de contrôler le type d'entrée) atteint une comparaison lâche (« » ) au lieu d’un strict (« = »).
Comme il apparaît, CodeWhisperer génère des conditions de comparaison lâches dans les suggestions de saisie semi-automatique :
La comparaison lâche du secret_token permet à un adversaire de contourner la comparaison $data["secret_token"] == "secret_token". Lors de la soumission d'un objet JSON avec la valeur « secret_token » étant True (booléen), le résultat de la comparaison lâche est également True (même sur les versions PHP les plus récentes).
Même en « suggérant » à l'outil (à l'aide d'un commentaire) d'écrire le chèque « en toute sécurité », il n'a pas généré d'extrait de code contenant une comparaison stricte -
Mise en œuvre correcte :
Ce n'est qu'en spécifiant une comparaison stricte (« === ») que nous sommes protégés contre les attaques de jonglage de types.
Les vulnérabilités dans le mécanisme « Mot de passe oublié » sont très courantes dans les applications SaaS et sont à l'origine de CVE telles que
Plus précisément, une vulnérabilité Unicode Case Mapping Collision se produit lorsque la saisie de l'utilisateur est en majuscule ou en minuscule dans une expression de comparaison alors que sa valeur d'origine est également utilisée dans le code. Certains caractères différents donneront le même code de caractère une fois transformés. Par exemple, « ß » et « SS » se transformeront tous deux en « 0x00DF » lorsqu'ils seront mis en majuscule.
De tels cas sont généralement utilisés pour contourner/induire en erreur certains contrôles de sécurité en trompant le contrôle de comparaison et en utilisant plus tard la valeur d'origine qui est différente de celle attendue. Ces cas sont assez difficiles à comprendre, surtout lorsque de nombreuses implémentations sont possibles.
Suggestion d'outil AI (GitHub Copilot) : un mécanisme particulièrement sensible à ce type de vulnérabilité est le mécanisme de mot de passe oublié . Il est courant de normaliser les saisies de l'utilisateur en majuscules ou en minuscules (dans les adresses e-mail ou les noms de domaine) lors de la vérification afin d'éviter des incohérences indésirables.
À titre d'exemple, un examen du code généré par Copilot pour la fonction de mot de passe oublié (ci-dessous) montre qu'il est vulnérable à ce problème.
Le code de Copilot recherche d'abord l'adresse e-mail en minuscule :
Ensuite, lors du suivi du reste du processus, il suggère ce qui suit :
Comme nous pouvons le voir, la suggestion de saisie semi-automatique génère un mot de passe aléatoire et l'envoie à l'adresse e-mail de l'utilisateur. Cependant, il utilise initialement une version minuscule de l'e-mail de l'utilisateur pour récupérer le compte utilisateur, puis continue d'utiliser l'e-mail de l'utilisateur d'origine « tel quel » (sans conversion en minuscules) comme cible de l'e-mail de récupération.
Par exemple, disons que l'attaquant possède la boîte de réception de « [email protected] » (le « K » est un caractère Unicode Kelvin Sign ) et utilise cet e-mail pour le mécanisme « Mot de passe oublié ». Les e-mails « [email protected] » et « [email protected] » ne sont pas équivalents « tels quels », mais après conversion en minuscules, ils le seront -
L'utilisation de l'adresse e-mail « [email protected] » permettra de récupérer les informations du compte « [email protected] » mais le mot de passe réinitialisé sera envoyé dans la boîte de réception de l'attaquant (« [email protected] »), permettant ainsi la prise de contrôle du « [email protected] ». [email protected] ».
Implémentation correcte : l'adresse e-mail utilisée pour récupérer le compte utilisateur doit être exactement égale à l'adresse e-mail de récupération (c'est-à-dire que le paramètre send_email utilisera également email.lower()).
De plus, par précaution, il est recommandé d'utiliser la valeur déjà stockée sur le serveur à la place de celle fournie par l'utilisateur.
Un autre sujet qui pourrait dérouter les outils de saisie semi-automatique est l'écriture de fichiers de configuration, en particulier pour les infrastructures cloud complexes.
La raison principale est qu’il existe certes des lignes directrices en matière de bonnes pratiques en matière de sécurité, mais que tout le monde ne les suit pas et, dans certains cas, il est nécessaire d’y aller à l’encontre de celles-ci. De tels échantillons de code pourraient se retrouver dans l’ensemble de données de formation de l’IA. Par conséquent, certaines sorties de saisie semi-automatique enfreindront les directives de sécurité recommandées.
Dans l'exemple suivant, nous allons créer un fichier de configuration pour un Pod Kubernetes , la plus petite unité d'exécution de Kubernetes.
Suggestion d'outil AI (Blackbox AI Code Generation) : dans cet exemple, nous commençons à créer un fichier de configuration de pod.
La suggestion crée la section des capacités et ajoute une fonctionnalité du noyau Linux (SYS_ADMIN) au conteneur, ce qui équivaut essentiellement à exécuter le pod en tant qu'utilisateur root.
Implémentation appropriée : est-il alors préférable d'omettre simplement le securityContext ? Non, puisque de nombreuses autres fonctionnalités seront alors ajoutées par défaut, violant ainsi le principe du moindre privilège .
Selon les meilleures pratiques de sécurité de Docker , la configuration la plus sécurisée consiste à supprimer d'abord toutes les fonctionnalités du noyau Linux, puis à ajouter ensuite celles requises pour votre conteneur.
Afin de résoudre les problèmes mentionnés ci-dessus, la section des capacités commence par supprimer toutes les capacités, puis ajoutera progressivement celles nécessaires à notre conteneur.
Cas d'utilisation : Objets de configuration – Incohérences conduisant à une désérialisation non sécurisée
De nombreuses vulnérabilités nécessitent la définition d'un objet de configuration, qui décide de la manière dont l'application gérera le composant requis.
L'exemple que nous avons choisi est la bibliothèque de désérialisation JSON de Newtonsoft en C#, qui peut être utilisée de manière sécurisée ou non en fonction de la configuration de l'objet JsonSerializerSettings, et plus particulièrement du TypeNameHandling .
Pendant que nous testions le code de désérialisation, nous avons remarqué que la définition de l'objet de configuration est assez aléatoire et que des changements subtils peuvent conduire à générer un ensemble de configuration différent.
Suggestions d'outils d'IA (Amazon CodeWhisperer) : les exemples suivants affichent un comportement incohérent basé uniquement sur les noms de méthodes utilisés par le développeur :
Nous pouvons voir deux configurations différentes qui entraînent toutes deux une désérialisation JSON non sécurisée en raison de la propriété TypeNameHandling étant « Auto » et « ALL ». Ces propriétés permettent au document JSON de définir la classe désérialisée, permettant ainsi aux attaquants de désérialiser des classes arbitraires. Cela peut être facilement exploité pour l'exécution de code à distance, par exemple en désérialisant la classe « System.Diagnostics.Process ». La seule différence dans le code original du développeur réside dans les noms de méthodes.
Mise en œuvre correcte :
Par défaut, un objet JsonSerializerSettings est instancié avec « TypeNameHandling = TypeNameHandling.None », qui est considéré comme sécurisé. Ainsi, nous utilisons le constructeur par défaut de JsonSerializerSettings qui donnera une valeur TypeNameHandling sûre.
Suggestion d'outil d'IA (copilote GitHub) :
Nous pouvons voir que le contrôle de sécurité est vraiment naïf, évitant uniquement les séquences point-point-slash nécessaires aux tentatives de traversée de répertoires. Le paramètre path peut être un chemin absolu - pointant vers n'importe quel chemin souhaité sur le système (par exemple, /etc/passwd), ce qui va à l'encontre de l'objectif du contrôle de sécurité.
Mise en œuvre correcte :
Ici, la fonction secure_file_read obtient un paramètre de nom de fichier (relatif) ainsi qu'un répertoire (safe_dir) où le nom de fichier doit résider (et qui ne doit pas être échappé). Il crée un chemin absolu en joignant safe_dir et filename et obtient sa forme canonique en appelant realpath. Il obtient ensuite la forme canonique du dossier safe_dir. Ensuite, le contenu du fichier est renvoyé uniquement si le chemin canonique commence par le safe_dir canonique.
Suggestion d'outil AI (génération de code Blackbox AI) : dans l'exemple suivant, nous avons demandé au compléteur automatique AI de générer une fonction chargée de filtrer les extensions de fichiers dangereuses.
Le code Python suggéré prend le nom de fichier, sépare l'extension et le compare à une liste d'extensions dangereuses.
À première vue, cet extrait de code semble sécurisé. Cependant, les conventions de nom de fichier Windows interdisent les noms de fichiers se terminant par un point, et lorsque vous fournissez un nom de fichier se terminant par un point (« . ») lors de la création du fichier, le point sera ignoré. Cela signifie que le filtre généré pourrait être contourné sous Windows lors de la soumission d'un fichier avec une extension malveillante suivie d'un point (ex. « exemple.exe »).
Implémentation appropriée : la meilleure approche consiste généralement à utiliser une liste blanche , qui vérifie l'extension du fichier uniquement par rapport aux extensions autorisées. Cependant, étant donné que les outils ont adopté une approche de liste noire (ce qui est nécessaire dans certains cas), nous présenterons une implémentation de liste noire plus sûre :
Dans l'extrait suivant, nous supprimons autant de contrôle que l'utilisateur avait sur l'entrée en supprimant l'extension de tous les caractères non alphanumériques et en la concaténant à un nom de fichier nouvellement généré.
En fin de compte : à utiliser avec prudence Les co-programmeurs automatiques sont parfaits pour suggérer des idées, compléter automatiquement le code et accélérer le processus de développement logiciel. Nous nous attendons à ce que ces outils continuent d'évoluer au fil du temps, mais pour l'instant, comme le démontrent les cas d'utilisation présentés dans cet article, les solutions d'IA à saisie semi-automatique ont de nombreux défis à relever jusqu'à ce que nous puissions nous rassurer et faire confiance à leurs résultats sans revérifier. insectes.
Un moyen possible de réduire le risque d’introduction de vulnérabilités consiste à demander délibérément à l’outil de génération de code IA de générer du code sécurisé. Bien que cela puisse être utile dans certains cas d'utilisation, ce n'est pas infaillible : le résultat « apparemment sécurisé » doit toujours être examiné manuellement par une personne compétente en sécurité logicielle.
Recommandations de l'équipe de recherche en sécurité de JFrog Pour aider à sécuriser votre logiciel, nous vous recommandons à la fois d'examiner manuellement le code produit par les outils génératifs d'IA, ainsi que d'incorporer des solutions de sécurité telles que les tests de sécurité des applications statiques (SAST) qui peuvent découvrir en toute confiance les vulnérabilités au fur et à mesure. Comme le montre l'exemple ci-dessus, les outils SAST peuvent alerter et détecter la collision de mappage de cas Unicode décrite dans le cas d'utilisation n°3. Cette approche proactive est essentielle pour assurer la sécurité de vos logiciels.
Restez à jour avec JFrog Security Research Les conclusions et les recherches de l'équipe de recherche en sécurité jouent un rôle important dans l'amélioration des capacités de sécurité des logiciels d'application de la plateforme JFrog.
Suivez les dernières découvertes et mises à jour techniques de l'équipe JFrog Security Research sur notre site Web de recherche et sur X @JFrogSecurity .