Sécurité du code GitHub Copilot : les failles XSS dans React
19 octobre 2023
0 minutes de lectureAvec l’évolution rapide de l’intelligence artificielle (IA) et des grands modèles de langage (LLM), des outils innovants tels que GitHub Copilot sont en train de révolutionner le paysage du développement logiciel. Dans un article précédent, j’ai abordé les implications de cette transformation, notamment les avantages offerts par ces outils intelligemment automatisés, mais aussi les nouvelles difficultés qu’ils posent en lien avec le maintien de la sécurité dans nos habitudes de codage. Snyk a également publié une étude de cas sur les risques de sécurité liés au codage assisté par l’IA.
Dans cet article, nous allons explorer les aspects liés à la sécurité de GitHub Copilot lorsqu’il est utilisé avec une base de code React et qu’il complète automatiquement le code des développeurs front-end, dans les fichiers JSX de leurs composants React. Nous cherchons à déterminer si le code proposé par GitHub Copilot adhère aux principes de codage sécurisé et aide les développeurs à écrire du code qui parvient à s’affranchir des vulnérabilités potentielles cross-site Scripting (XSS), en particulier dans le contexte du développement React.
Pour ceux qui ne sont pas familiers avec les implications majeures des failles XSS, sachez qu’elles permettent aux attaquants d’injecter des scripts côté client dans les pages Web consultées par d’autres utilisateurs.
Les développeurs adoptent GitHub Copilot
Le développement logiciel est loin d’être épargné par l’influence transformatrice de l’IA. L’une des innovations émergentes en la matière est GitHub Copilot, un outil de développement intégré à l’IDE, par exemple à VS Code, et basé sur la technologie Codex d’OpenAI.
GitHub Copilot est une IA pensée pour fonctionner comme un collègue programmeur. Cet outil est capable de proposer des suggestions de codage, d’écrire des lignes ou des blocs de code entiers, et est compatible avec plusieurs langages, notamment des bibliothèques et frameworks front-end populaires, tels que React.
Copilot gagnant en popularité chez les développeurs, nous devons pouvoir faire confiance au code qu’il nous suggère, aussi bien en matière de syntaxe que de sécurité, en nous assurant que ce code est suffisamment sécurisé pour fonctionner conformément aux normes auxquelles nous nous astreignons et éviter de mettre les utilisateurs en danger.
En s’appuyant sur des exemples de code JavaScript et un examen approfondi des pratiques de sécurité spécifiques à JSX et React, cet article entend non seulement informer les développeurs, mais aussi leur permettre d’évaluer et d’utiliser ces outils de développement augmentés par l’IA de manière responsable, sans compromettre la sécurité des applications.
Terrain miné : utiliser la fonction dangerouslySetInnerHTML de React
React propose une API qui permet aux développeurs de définir le code HTML directement à partir d’un composant React : la fonction `dangerouslySetInnerHTML`.
Comme son nom l’indique, l’utilisation de cette fonction peut s’avérer dangereuse. En effet, elle contourne les contrôles de sécurité de React qui assurent l’encodage des sorties destiné à les protéger contre les vulnérabilités XSS, ce qui vous permet donc d’insérer manuellement du HTML dans un composant. Cette fonction peut être utile dans certains cas, notamment lorsqu’il s’agit de traiter du contenu textuel enrichi provenant de sources fiables. Considérons l’exemple suivant :
1function MyComponent() {
2 return <div dangerouslySetInnerHTML={{__html: '<h1>Hello World</h1>'}} />;
3}
Bien qu’il s’agisse d’un moyen rapide et direct de gérer le contenu HTML, il expose également votre application à un risque d’attaques cross-site scripting (XSS). Si les données fournies par l’utilisateur font partie du contenu HTML défini par `dangerouslySetInnerHTML`, un pirate peut injecter un script arbitraire dont l’exécution pourra avoir de lourdes répercussions.
1function MyComponent({userInput}) {
2 // This can expose the application to XSS risks if userInput contains a malicious script.
3 return <div dangerouslySetInnerHTML={{__html: userInput}} />;
4}
Il convient donc de manipuler `dangerouslySetInnerHTML` avec la plus grande prudence dans son application.
GitHub Copilot suggère-t-il du code sécurisé ?
GitHub Copilot peut-il aider les développeurs à implémenter certains de ces contrôles de sécurité pour contrer les vulnérabilités XSS liées à l’utilisation de `dangerouslySetInnerHTML` ?
Examinons le code du composant React suivant, qui utilise la directive `dangerouslySetInnerHTML` :
1 <Row className="justify-content-between">
2 <Col md="6">
3 <Row className="justify-content-between align-items-center">
4 <div
5 dangerouslySetInnerHTML={{
6 __html: `
7 <img src=${database.authorScreenshotURL}
8 alt=${
9 authorScreenshotDescription
10 } />
11 `,
12 }}
13 />
14 </Row>
La variable `authorScreenshotDescription` qui « entre » dans ce composant est contrôlée par l’utilisateur et sert à spécifier la description textuelle de l’image.
Un attaquant pourrait exploiter cette vulnérabilité XSS pour exécuter du code JavaScript dans le navigateur en définissant la valeur de la variable `authorScreenshotDescription` comme suit :`\"
Cela dit, vous êtes un développeur responsable et avez donc bien conscience de ce problème :
il faut vraiment éviter d’utiliser `dangerouslySetInnerHTML`, puisque comme son nom l’indique, c’est dangereux ! Il y a pourtant un cas d’utilisation spécifique qui vous contraint à l’utiliser, et donc…
Vous savez que s’il vous faut vraiment utiliser cette API React, vous devrez implémenter un encodage de sortie sécurisé pour échapper les caractères dangereux comme `<`, qui permettent de créer des éléments HTML.
Quelle est ensuite la marche à suivre ? Vous commencez à écrire une fonction rapide d’échappement XSS pour ce composant React. GitHub Copilot est bien évidemment là pour vous aider :
Vous commencez à taper le nom de la fonction et ses arguments, et au moment où vous êtes sur le point de commencer à coder le corps de la fonction, GitHub Copilot vous suggère automatiquement le code suivant. Ça a l’air parfait. Il suffit d’appuyer sur la touche TAB, et le tour est joué. Bien entendu, il ne faut pas oublier de mettre à jour notre utilisation de l’API `dangerouslySetInnerHTML` dans le composant afin d’utiliser cette fonction d’échappement de sécurité :
Si nous essayons de renouveler l’exploit XSS précédent, nous nous apercevons qu’il échoue :
Apparemment, la complétion de code suggérée par GitHub Copilot pour la fonction `escapeCrossSiteScripting` fonctionne à merveille et a effectivement permis un échappement des chevrons qui avaient précédemment créé un nouvel élément HTML `<img />` et exécuté du code JavaScript.
Mais nous ne sommes pas encore sortis d’affaire !
Les attaquants sont tenaces et il ne leur faut pratiquement aucun effort pour automatiser leurs charges utiles. Ils peuvent ainsi essayer des milliers de permutations de chaînes de caractères, une pratique que l’on désigne souvent sous le terme de « fuzzing », afin de trouver une méthode qui fonctionne. C’est là toute la force du piratage informatique : alors que les développeurs doivent se protéger contre tous les points de défaillance, il suffit aux attaquants de trouver un seul point d’entrée, aussi réduit soit-il.
Un attaquant pourrait donc essayer la charge utile suivante :
1s \"<img src=x onLoad=alert(1)
Dans l’exemple ci-dessus, nous avons remplacé le gestionnaire d’attributs `onError` par un gestionnaire `onLoad`. Si nous chargeons à nouveau la page Web, nous constatons que cette attaque a réussi et qu’une fenêtre contextuelle est apparue :
En tant que développeur, vous vous demandez peut-être comment cette faille de sécurité apparaît et comment vous pouvez l’éviter. Un développeur React expérimenté pourrait vous dire qu’une convention de codage bien supérieure en termes de sécurité consiste à entourer la valeur d’un attribut avec des guillemets. Nous allons donc procéder à ce changement pour la valeur de l’attribut `alt=` :
1 <div
2 dangerouslySetInnerHTML={{
3 __html: `
4 <img src=${database.authorScreenshotURL}
5 alt="${escapeCrossSiteScripting(
6 authorScreenshotDescription
7 )}" />
8 `,
9 }}
10 />
En procédant de la sorte et en appliquant la charge utile `s \"
Ça a l’air parfait.
Jusqu’à ce que…
Un attaquant trouve un moyen créatif d’échapper à cette situation sous la forme d’une charge utile plus courte qui conserve l’exploitation de l’attribut spécial `onLoad` et injecte un point-virgule de fin et une chaîne `//` pour symboliser le fait que toute chaîne postérieure doit être traitée comme un commentaire.
En utilisant la charge utile suivante :
1s \" onLoad=alert(1); //
Voici une nouvelle exploitation de vulnérabilité XSS réussie :
Mais que se passerait-il si nous adoptions une approche différente ?
Et si nous n’entourions pas l’attribut `alt=` de guillemets doubles à l’intérieur de la section `dangerouslySetInnerHTML` et que nous tentions plutôt de remanier la fonction d’échappement ?
C’est ce que nous allons essayer de faire. Le code JSX de notre composant React reste donc le suivant :
1 <div
2 dangerouslySetInnerHTML={{
3 __html: `
4 <img src=${database.authorScreenshotURL}
5 alt=${escapeHTML(
6 authorScreenshotDescription
7 )} />
8 `,
9 }}
10 />
Nous souhaitons ensuite remanier la fonction `escapeCrossSiteScripting` existante pour en faire une fonction d’échappement HTML appropriée, plus sûre, qui encode davantage de caractères que les seuls `<`, `>` et `&`.
Alors que nous commençons à taper le code, GitHub Copilot sort de sa torpeur et propose la suggestion suivante :
En fait, il a suggéré l’intégralité du code du corps de la fonction, et je l’ai simplement invité à continuer à compléter les suggestions de code jusqu’à la finalisation de notre code de nettoyage. Cela semble correspondre à une meilleure logique d’encodage de sortie, prenant en compte d’autres caractères dangereux comme les guillemets simples et les guillemets doubles.
Si nous avions fourni à l’application la charge utile suivante :
1s \"<img src=x onError=alert(1)
Le code suggéré par GitHub Copilot aurait encodé tous les caractères potentiellement dangereux, y compris le guillemet double que l’injection de code de la charge utile a tenté d’exploiter :
Notre approche serait pourtant à nouveau erronée, car une charge utile aussi simple que celle ci-dessous déclencherait quand même l’attaque XSS permettant l’exécution d’un code JavaScript :
1s onLoad=alert(1)
Mais pourquoi ?
Mise en évidence des problèmes de sécurité XSS de React
Aussi complète que soit cette dernière tentative de fonction d’échappement HTML, elle passe à côté d’un principe fondamental de l’échappement de sortie et de la logique générale de sécurité du nettoyage : l’importance cruciale du contexte.
Dans le cas présent, la valeur de l’attribut fait partie d’un formulaire d’attribut HTML et non d’un élément HTML. Le fait que nous puissions simplement utiliser un caractère d’espacement pour indiquer la fin de l’attribut `alt` et le début de l’attribut suivant `onLoad` est au cœur même du problème.
Se protéger des attaques XSS dans les applications React
Même si des outils comme GitHub Copilot révolutionnent le développement logiciel, il est primordial de garder à l’esprit que ces modèles d’IA peuvent suggérer un code non vulnérable. Il est donc essentiel de disposer de solides filets de sécurité pour protéger vos applications. C’est précisément là qu’interviennent les outils de sécurité des développeurs tels que Snyk.
Snyk, qui jouit d’une excellente réputation dans le monde des outils de sécurité destinés aux développeurs, propose une extension IDE extrêmement pratique. Au fur et à mesure que vous écrivez votre code React, Snyk parvient à repérer les vulnérabilités de sécurité potentielles, notamment celles découlant de l’utilisation non sécurisée de `dangerouslySetInnerHTML`, que Copilot est susceptible de ne pas détecter. Cette protection en temps réel peut s’avérer déterminante pour vous aider à écrire des applications React sécurisées et à prévenir les attaques XSS.
Un des éléments clés qui permet à Snyk de se démarquer est DeepCode AI. DeepCode utilise des résultats alimentés par l’IA pour analyser votre code au fur et à mesure que vous l’écrivez, en identifiant les problèmes de sécurité, de performance et de logique. Il ne se contente toutefois pas d’identifier les problèmes potentiels. DeepCode AI propose également AI Fix, qui génère automatiquement des correctifs pour les problèmes identifiés, ce qui vous permet de coder encore plus facilement de manière sécurisée et efficace.
Snyk est un outil gratuit conçu pour aider les développeurs à créer un code plus sûr à partir de leur IDE. Si vous plongez dans le monde du codage assisté par l’IA avec des outils tels que GitHub Copilot, la protection complémentaire offerte par des outils tels que Snyk peut représenter un atout inestimable.
Parallèlement à l’utilisation de Snyk, il convient de respecter les pratiques de sécurisation des applications Web suivantes. L’un de ces contrôles de sécurité concerne le recours à un encodage de sortie sécurisé.
L’atténuation de ces risques de sécurité dans les applications Web front-end passe par un nettoyage méticuleux des saisies utilisateur qui transitent par des API sensibles comme `dangerouslySetInnerHTML`. Les saisies utilisateur doivent toujours être considérées comme non fiables. Tout contenu HTML provenant de l’utilisateur doit par conséquent être soumis à un processus de nettoyage HTML robuste. Plusieurs bibliothèques tierces, telles que DOMPurify, peuvent être utilisées à cet effet :
1import DOMPurify from "dompurify";
2
3function MyComponent({userInput}) {
4 const cleanInput = DOMPurify.sanitize(userInput);
5 return <div dangerouslySetInnerHTML={{__html: cleanInput}} />;
6}
Par ailleurs, si vous cherchez simplement à obtenir un rendu de contenu textuel, vous pouvez envisager des alternatives à `dangerouslySetInnerHTML`, en utilisant par exemple la gestion du contenu textuel de React, accessible avec des accolades `{}`.
Conclusion
Pour résumer, même si les suggestions de l’IA peuvent accélérer votre processus de codage, il demeure indispensable de s’appuyer sur la vigilance humaine et sur des outils de sécurité robustes pour créer des applications sécurisées.
Gardez également à l’esprit que même si `dangerouslySetInnerHTML` offre un moyen de gérer le HTML dans React, vous devez vous assurer de bien appréhender ses implications potentielles en matière de sécurité. En faisant preuve de prudence lors de l’utilisation et du nettoyage des données qui y circulent, vous pouvez sécuriser vos applications React contre les vulnérabilités XSS.
Je vous recommande vivement d’essayer Snyk et de constater par vous-même à quel point ses évaluations de la vulnérabilité en temps réel et ses correctifs automatiques peuvent améliorer la sécurité de vos applications React et, de surcroît, votre productivité en tant qu’ingénieur sensible à l’élaboration de logiciels sécurisés. Pour reprendre un adage bien connu, mieux vaut prévenir que guérir, surtout lorsqu’il s’agit de codage sécurisé.
Cap sur la capture du drapeau
Découvrez comment résoudre les défis de capture du drapeau en regardant notre atelier virtuel à la demande.