Chris Rohlf
Au cours de l'année écoulée, nous avons publié de nombreuses données montrant que les modèles d'IA de pointe sont tout à fait capables de découvrir et d' exploiter des vulnérabilités de sécurité. Cependant, montrer comment des vulnérabilités jusqu'alors inconnues pourraient être exploitées n'est que le début de ce que les modèles d'IA de pointe peuvent faire pour la cyberdéfense. Dans cet article, nous présentons un résumé de certaines de nos recherches initiales sur l'analyse et la réduction automatisées de la surface d'attaque : le processus consistant à utiliser des modèles d'IA pour cartographier le code qui est atteignable, puis à supprimer ou restreindre l'accès à tout ce qu'un déploiement donné n'a pas besoin d'exposer.
L'analyse et la réduction automatisées de la surface d'attaque peuvent améliorer significativement la sécurité des logiciels déployés à court terme. Sécuriser les logiciels en trouvant et en corrigeant des vulnérabilités individuelles seulement passe mal à l'échelle : corriger ne serait-ce qu'une vulnérabilité critique ne vous apprend rien sur les vulnérabilités qui demeurent. En revanche, réduire l'atteignabilité du code peut diminuer la gravité des vulnérabilités inconnues, y compris celles qui pourraient faire l'objet d'une exploitation active. Ces techniques ne suppriment pas les vulnérabilités inconnues dans le code, mais elles rendent ces vulnérabilités plus difficiles à atteindre, et réduisent ainsi leur gravité. Minimiser la surface d'attaque des bases de code permet d'obtenir une meilleure sécurité sans jamais avoir à trouver ni à corriger une vulnérabilité individuelle.
Ci-dessous, nous présentons un aperçu de certaines des données que nous avons collectées en appliquant des techniques de réduction de la surface d'attaque à de grandes bases de code open source à l'aide de Claude. Notre objectif ce faisant est de montrer que Claude peut améliorer de manière significative et économique la sécurité des logiciels grâce aux méthodes que nous décrivons. Plus précisément, nous avons exécuté Claude sur 25 projets (dans une variété de langages), et il a découvert 493 problèmes. Avec ces résultats à l'esprit, nous expliquons comment les développeurs peuvent appliquer des techniques similaires à leur propre code.
Méthodes pour réduire la surface d'attaque des bases de code
Avec le temps, un logiciel a tendance à devenir plus compliqué, avec de nouvelles couches d'abstractions, de nouvelles dépendances et de nouvelles fonctionnalités qui s'empilent lentement les unes sur les autres. Cela peut souvent faire qu'il fonctionne très différemment de ce que ses développeurs d'origine avaient prévu. Et c'est en grande partie la raison pour laquelle les attaquants ont souvent tant de succès : ils étudient exactement comment un morceau de code se comporte en pratique, plutôt que comment ses développeurs comptaient ou avaient conçu qu'il fonctionne.
Les techniques d'analyse et de réduction automatisées de la surface d'attaque visent à découvrir les surfaces d'attaque inutiles, puis à placer ce code derrière un contrôle approprié. Cela peut impliquer de déplacer du code de la phase pré-authentification à la phase post-authentification, de 0-clic à 1-clic, d'activé-par-défaut à activation explicite, ou du premier message d'un protocole vers un point plus profond de la machine à états de session. Une partie du code est censée être atteignable avant ces étapes, mais une grande partie ne l'est pas, et, compte tenu de la complexité qu'entraîne le remaniement du code sur des milliers ou des millions de lignes de code, l'exposition inutile de fonctionnalités passe souvent inaperçue.
Il existe de nombreuses façons de réduire la surface d'attaque du code. Dans nos propres efforts de recherche, nous nous sommes concentrés en particulier sur trois méthodes :
- Configuration par défaut. Nous avons identifié du code qui était atteignable dans sa configuration par défaut livrée mais qui n'était pas destiné à être utilisé par une fonctionnalité par défaut.
- Interaction entre configurations. Nous avons trouvé des paires d'options qui semblaient raisonnables prises individuellement, mais qui causaient des problèmes lorsqu'elles étaient configurées ensemble (par exemple, en désactivant silencieusement un contrôle de sécurité, ou en laissant le comportement d'une fonctionnalité non défini alors que son code reste atteignable).
- Atteignabilité précoce. Nous avons identifié du code sensible en matière de sécurité et complexe qui s'exécute soit avant un contrôle de sécurité approprié (comme l'authentification), sans interaction de la victime, soit avant une transition d'état du protocole.
Nous voulions comprendre la capacité de Claude à partir de ces trois descriptions de surface d'attaque à découvrir de vrais problèmes dans des bases de code vastes et complexes. Nous avons analysé 25 projets open source (représentant plus de cinq millions de lignes de code), écrits dans des langages comprenant C, C++, Java, PHP et Python. Claude a créé un inventaire de plus de 17,000 options de configuration dans ces projets. Parmi celles-ci, 3,842 étaient liées à la configuration d'un contrôle ou d'une fonctionnalité pertinente pour la sécurité.
Aux fins de notre recherche, nous avons réparti ces projets open source en trois grandes catégories :
- Public. Ces projets sont conçus pour servir tout utilisateur pouvant les atteindre, ou pour consommer des données rédigées par des parties qu'ils ne peuvent identifier. (Des exemples incluent un résolveur DNS analysant des réponses en amont, ou un proxy analysant des réponses d'origine.)
- Contrôlé. Ces projets sont destinés à servir des utilisateurs authentifiés (tels que ceux disposant d'un mot de passe, de détenteurs de clés ou de certificats, ou d'appareils enregistrés). Le logiciel permet toujours des requêtes client arbitraires, mais aucune fonctionnalité n'est disponible tant que l'utilisateur n'a pas été authentifié.
- Privé. Ceux-ci sont construits et déployés pour servir l'environnement privé de l'opérateur lui-même. Dans ces cas, l'ajout d'un contrôle comme une couche d'authentification est optionnel, mais hors du périmètre du logiciel.
Ces catégories aident à contextualiser nos résultats. Par exemple, on s'attendrait à ce qu'un pourcentage significatif d'une base de code Public soit accessible dans la configuration par défaut (puisque elle est conçue pour servir n'importe quel utilisateur), mais que ce soit l'inverse pour les projets de la catégorie Gated.
Conclusions de Claude
Claude a trouvé 493 problèmes en utilisant les trois méthodes décrites ci-dessus. Plusieurs thèmes nous ont marqués dans ses résultats.
Premièrement, Claude a constaté que les onze projets gated contenaient du code accessible involontairement (bien que ce code ne représentait pas un pourcentage significatif du total des lignes de code pour aucun de ces projets). Plus précisément, Claude a découvert 93 surfaces exposées avant authentification inutiles dans la configuration par défaut de la cohorte gated, 37 endroits où du code s'exécutait ou où un état était enregistré avant l'authentification, et 61 combinaisons de configuration qui affaiblissaient silencieusement un contrôle avant authentification configuré par l'administrateur. Aucun de ces problèmes n'aurait été immédiatement exploitable à lui seul, mais ils créent une surface d'attaque bien plus grande que nécessaire.
Deuxièmement, le travail de Claude sur l'atténuation des problèmes d'interaction entre configurations a produit certains des résultats les plus intéressants et les plus marquants (pour tous les types de projets). Claude a trouvé 162 combinaisons de configuration sensibles en matière de sécurité qui étaient acceptées à la fois sans erreur ni avertissement et sans aucune documentation du comportement attendu de leur interaction. Dans 94 de ces cas, la combinaison désactivait silencieusement un contrôle de sécurité. Les 68 autres cas étaient des incohérences dans les mêmes mécanismes de sécurité (par exemple, un écouteur ou un chemin de connexion restait accessible alors que la configuration indiquait qu'il était désactivé ; deux paramètres entraient en conflit et celui qui prenait le dessus dépendait de leur ordre dans le fichier de configuration, ou une protection configurée par l'administrateur ne faisait rien alors que son code restait accessible). Bien que certaines de ces combinaisons de configuration puissent être non standard ou rarement utilisées, rien n'empêcherait un administrateur de ces projets de configurer et de déployer le code de cette manière.
Claude a également trouvé un schéma précis qui est apparu indépendamment dans quatre projets sans lien entre eux. Cela fonctionnerait comme suit. Un administrateur écrirait une règle d'accès pour les clients distants en utilisant un nom d'hôte ou un motif générique, au lieu d'une adresse IP. Lorsqu'un client se connecte, le serveur comparerait cette règle au nom d'hôte du client. Que le serveur ait ou non appris ce nom d'hôte dépendrait d'une option distincte qui contrôlait les recherches DNS inverses (que la plupart des projets avaient désactivées). Avec les recherches désactivées, le serveur n'aurait que l'adresse IP du client. Le serveur comparerait la règle à cette adresse en texte brut. Mais un motif de nom d'hôte ne peut jamais correspondre à une adresse IP. Cela signifie qu'une règle de refus ne se déclencherait jamais, et que la liste échouerait ouverte. Certains des projets concernés mentionnent dans leur documentation que les règles de nom d'hôte dépendent de l'activation des recherches DNS, mais aucun ne précise qu'une liste de refus écrite de cette manière aurait un comportement d'échec ouvert.
Troisièmement, Claude a souvent découvert que deux options de configuration entraient en conflit de manière à supprimer silencieusement un contrôle de sécurité. Dans un cas, un administrateur pouvait exiger de chaque client qui se connecte qu'il prouve son identité avec un certificat. Dans l'autre, le code n'était censé que différer ce contrôle jusqu'à ce qu'il soit nécessaire, mais en fait le désactivait complètement. Un serveur configuré pour n'accepter que des clients de confiance acceptait donc une connexion de n'importe qui. Cela exposait un large ensemble de code de traitement des requêtes (qui était censé être derrière le contrôle par certificat) à des clients non authentifiés. Le logiciel acceptait les deux options de configuration ensemble sans avertissement dans les paramètres par défaut ; la documentation ne mentionnait jamais que l'exigence de certificat avait été abandonnée. Cet ensemble de options de configuration en conflit était présent dans le logiciel depuis 21 ans.
Enfin, Claude a découvert un problème dans un projet qui laissait l'authentification désactivée par défaut (et attendait que l'administrateur l'active). L'option qui exigeait des certificats clients TLS était honorée sur la plupart des points de terminaison réseau du projet, mais silencieusement ignorée sur quelques autres (ceux qui chargeaient leurs paramètres TLS via un chemin avec un espace de noms différent). Cela signifiait qu'un client sans certificat pouvait compléter la poignée de main TLS et ouvrir une session sur un point de terminaison concerné. Rien n'était consigné dans les journaux au sujet du paramètre ignoré. Que l'option prenne effet ou non dépendrait de l'espace de noms utilisé par l'administrateur dans la configuration. Une forme du nom du paramètre fonctionne sur chaque point de terminaison, mais la forme plus courte — que la plupart des exemples utilisent — est abandonnée sur les points concernés.
Étude de cas : OpenSSH
De façon surprenante, mesuré en pourcentage du total des lignes de code, le projet pour lequel Claude a obtenu la plus grande réduction de la surface d'attaque était OpenSSH. OpenSSH est une base de code C de haute qualité qui fournit un accès distant sécurisé. Claude a retracé les chemins d'entrée du démon SSH dans la version 10.4 et a trouvé environ 4,500 lignes de code nouveau et expérimental pour ML-DSA-44, un algorithme de signature post-quantique, qu'un client non authentifié pouvait atteindre dans la configuration par défaut. ML-DSA n'est pas activé par défaut, mais le code restait atteignable malgré tout, même lorsqu'un administrateur excluait explicitement l'algorithme. Le démon analysait le type de clé que le client envoyait, (et, pour les certificats, il vérifiait aussi la signature CA intégrée). Ce n'est qu'ensuite qu'il vérifiait si ce type de clé était autorisé. Il n'y a aucune bonne raison pour que ce code complexe et non sûr pour la mémoire soit atteignable lorsqu'il n'a pas été activé par la configuration. Ces 4,500 lignes de code dans ML-DSA-44 représentent environ 16% du code de pré-authentification propre d'OpenSSH dans un sshd par défaut. Nous l'avons signalé aux développeurs d'OpenSSH avec des correctifs. La version 10.5 a déplacé la vérification avant l'analyse.
Claude a ensuite noté deux lacunes dans la vérification de la 10.5. La première était une incohérence entre ce que le client prétendait et ce qu'il envoyait. La vérification examinait le nom de l'algorithme annoncé par le client, mais l'analyseur était choisi par une chaîne de type intégrée dans le blob de clé. Un client pouvait annoncer ssh-ed25519, passer la vérification, et envoyer quand même une clé ML-DSA qui serait analysée.
La deuxième lacune concernait les certificats dans la vérification de la 10.5. Les types de clés de certificat étaient activés par défaut. Étant donné un certificat fourni par un attaquant, sshd désérialisait la clé publique CA intégrée et effectuait une vérification complète de la signature avec celle-ci, pour tout algorithme pris en charge, avant de consulter la configuration. Le chemin de signature de la clé utilisateur était correctement protégé par la liste des clés publiques autorisées à se connecter au serveur, mais le chemin de signature CA ne l'était pas. Cela rendait chaque vérificateur de signature de clé publique du binaire atteignable par tout client pouvant compléter un échange de clés. Nous avons construit une preuve de concept et travaillé à travers plusieurs itérations d'un correctif avec les mainteneurs. La modification résultante a placé les vérifications à l'intérieur de l'analyseur lui-même. Le type de clé encodé et l'algorithme de signature CA du certificat sont désormais comparés aux listes d'autorisation configurées avant la désérialisation ou la vérification de signature correspondante.
Ce correctif a supprimé environ un cinquième du code d'OpenSSH qu'un client non authentifié pouvait auparavant atteindre dans une build par défaut. Presque tout cela était le nouveau code ML-DSA, qui est désormais inatteignable même pour un client qui ment sur son type de clé ou cache une clé CA ML-DSA dans un certificat par ailleurs acceptable. Les certificats signés avec un algorithme CA autorisé sont toujours analysés et vérifiés, mais la vérification s'exécute maintenant avant l'analyse. Les mainteneurs ont livré les modifications décrites ici dans la version 10.6.
OpenSSH fait partie du code le plus audité au monde. Si une atteignabilité inutile y survit, elle peut survivre n'importe où. Cela suggère que la réduction de la surface d'attaque des bases de code est susceptible de porter ses fruits dans de nombreuses bases de code importantes.
Conclusion
Il est peut-être impossible de mesurer combien de vulnérabilités inconnues se trouvent dans une base de code donnée. Et il existe beaucoup de données montrant que les attaquants peuvent et vont trouver ces vulnérabilités avec suffisamment de temps (en particulier s'ils sont équipés de modèles d'IA de pointe). Mais un défenseur qui réduit la surface d'attaque inutile de son code réduit en même temps le risque lié à toutes les vulnérabilités inconnues de ce code — y compris celles qui sont déjà discrètement exploitées. OpenSSH fait partie du code C le plus audité au monde. Pourtant, il contenait environ 4,500 lignes de code expérimental d'analyse de signatures qui pouvaient être atteintes par un attaquant avant toute vérification de configuration.
Nous avons combiné les techniques de réduction de la surface d'attaque décrites ci-dessus en une seule compétence Claude réutilisable, que nous travaillons à intégrer dans OSS-Scanner. La compétence effectue trois passes sur une base de code. Chaque fois, elle repart de zéro (avec une fenêtre de contexte vide), à l'exception d'un court élément de contexte qu'elle reçoit de la passe précédente.
Dans la première passe, la compétence examine s'il existe du code que la configuration par défaut permet d'atteindre mais qui n'est utilisé par aucune fonctionnalité prévue. Dans la deuxième passe, elle examine le code qui s'exécute avant toute barrière stricte (comme l'authentification) ou avant les points d'entrée de la machine à états du protocole, dans n'importe quelle configuration. Enfin, troisièmement, la compétence évalue quelles combinaisons de configurations désactivent silencieusement un contrôle de sécurité ou en dégradent l'efficacité. Dans chaque passe, Claude crée un inventaire des points d'entrée à partir du code et confirme chaque constat avec une trace dynamique du programme. Pour les constats liés à la configuration par défaut, il conçoit et implémente une barrière et s'assure que cela n'entraîne aucun changement dans le comportement par défaut du programme. Il le fait en exécutant la suite de tests propre du projet (ainsi qu'une suite de régression boîte noire) sur la version de référence ; il exige que les versions modifiées donnent des résultats identiques. Toute barrière qui changerait une configuration non par défaut est mise de côté pour qu'un humain décide. Pour les deux autres passes, il propose des modifications comme la réorganisation d'une vérification de configuration, l'ajout d'une garde pour imposer l'ordre des états, ou le rejet de la combinaison au chargement de la configuration. Même sans utiliser cette compétence spécifique, nous pensons que la méthodologie générale décrite ici est susceptible d'être efficace.
Les techniques décrites dans cet article n'offrent qu'une vision très limitée de ce qui est possible en matière de réduction de la surface d'attaque. Par exemple, Claude est également capable d'analyser et de comparer les journaux d'exécution aux configurations afin de garantir que seules les fonctionnalités requises ont été activées : la désactivation des analyseurs inutilisés, des protocoles obsolètes et des tableaux de bord réduit le risque de sécurité à moindre coût.
Nous encourageons les organisations à explorer l'utilisation de Claude pour analyser et réduire la surface d'attaque des logiciels dont elles dépendent. Nous pensons qu'il s'agit là de l'un des moyens les plus concrets dont disposent les développeurs pour améliorer de manière mesurable la sécurité du code sur lequel le monde fonctionne déjà.