5 moyens pour empêcher une injection de code dans JavaScript et Node.js
6 avril 2021
0 minutes de lectureÉcrire du code JavaScript sécurisé de manière à empêcher l’injection de code peut sembler être une tâche totalement banale. Un certains nombre d’écueils sont toutefois à éviter. Vous (un développeur) avez beau respecter les meilleures pratiques de sécurité, d’autres n’en font peut-être pas de même. Vous utilisez probablement des paquets open source dans votre application. Comment savoir si ceux-ci ont été développés en toute sécurité ? Et s’ils contenaient un code non sécurisé comme eval()
? Penchons-nous sur la question.
Qu’est-ce qu’une injection de code ?
Une injection de code est une forme spécifique d’attaque par injection large, lors de laquelle un pirate peut envoyer du code JavaScript ou Node.js qui est interprété par le navigateur ou l’environnement d’exécution Node.js. La faille de sécurité se manifeste lorsque l’interpréteur est incapable de faire la distinction entre le code sain prévu par le développeur et le code injecté en entrée par le pirate.
Comment empêcher une injection de code
C’est une convention essentielle d’un codage sécurisé : n’autorisez aucune exécution de code dynamique dans l’application. Vous devez donc éviter les constructions de langage telles que eval
et les chaînes de code transmises à setTimeout()
ou au constructeur Function
. Deuxièmement, évitez la sérialisation qui pourrait être vulnérable aux attaques par injection exécutant du code dans le processus de sérialisation. Enfin, effectuez une analyse des dépendances pour vous assurer que votre application n’est pas sensible à cette attaque par le biais de composants open source tiers. En outre, si vous utilisez un outil d’analyse de code statique comme Snyk Code, vous pouvez détecter ces failles de sécurité potentielles concernant l’injection de code dans votre code ou dans celui de vos collègues.
Dans cet article, nous passerons en revue 5 moyens d’empêcher une injection de code :
Éviter
eval()
,setTimeout()
etsetInterval()
Éviter
new Function()
Éviter la sérialisation de code dans JavaScript
Utiliser un linter de sécurité Node.js
Utiliser un outil d’analyse de code statique pour détecter et résoudre les problèmes d’injection de code
1\. Éviter eval()
, setTimeout()
et setInterval()
Je sais ce que vous pensez : encore un guide qui me dit d’éviter eval
! C’est vrai, mais je veux aussi vous donner des exemples concrets d’autres bibliothèques populaires ayant utilisé eval
(ou d’autres formes de construction de code) qui ont donné lieu à une faille de sécurité importante.
Avant de nous plonger dans les références de paquets tiers vulnérables, expliquons d’abord ce qu’est eval
et les fonctions qui l’accompagnent. Les environnements d’exécution JavaScript, comme le navigateur et la plateforme Node.js côté serveur, permettent d’évaluer et d’exécuter le code au moment de l’exécution. En voici un exemple concret :
const getElementForm = getElementType == “id” ? “getElementById” : “getElementByName”;
const priceTagValue = eval(“document.”+getElementForm+”(“+elementId+”).value”);
Nous voyons ici qu’un programmeur essaie d’accéder aux données sur le DOM de façon dynamique. Dans cet exemple, l’hypothèse est que getElementform
peut également être contrôlé par l’utilisateur, de même que la variable elementId
. Il existe de meilleures façons d’effectuer cette tâche sans eval
, et nous ne saurions que trop vous conseiller d’éviter par tous les moyens un code dynamique comme celui-ci.
Pour ce qui est de Node.js, il est possible d’autoriser l’accès à des points de données spécifiques dans l’application, sur la base d’une évaluation dynamique. En voici un exemple :
const db = "./db.json"
const dataPoints = eval("require('"+db+"')");
Dans cet exemple, partons du principe que le fichier requis est dynamique et potentiellement contrôlé par l’utilisateur. Il est donc susceptible de contenir des vulnérabilités de sécurité d’injection de code.
Un exemple concret d’utilisation non sécurisée d’eval révélé par une injection de code dustjs
Le paquet npm de LinkedIn dustjs, un projet de modèles asynchrones pour le navigateur et Node.js côté serveur, montre à quel point une vulnérabilité d’injection de code peut devenir grave.
Bien que ce paquet ne soit plus maintenu correctement, il affiche toujours environ 72 000 téléchargements mensuels et a subi une vulnérabilité de sécurité par injection de code.
Les responsables de dustjs ont tout fait pour échapper les entrées utilisateur potentiellement dangereuses pouvant se retrouver dans des constructions de code non sécurisées comme la fonction eval()
. Toutefois, la fonction escapeHtml
elle-même comportait une faille de sécurité, car elle ne vérifiait que les types de chaînes, puis échappait l’entrée, alors qu’elle aurait dû également vérifier d’autres types, comme les tableaux. Cette requête d’extraction a corrigé la vulnérabilité de sécurité d’injection de code :
Vous vous demandez sans doute quel est le rôle d’eval
() dans tout cela ?
Si vous utilisez dustjs, vous pouvez également introduire le paquet npm dustjs-helpers pour obtenir des assistants supplémentaires pour le modèle, comme les opérations mathématiques et les opérations logiques. L’un de ces assistants est une condition if
, que vous pouvez utiliser telle quelle dans vos propres fichiers de modèles dust :
Logique, non ?
Le problème est que l’entrée utilisateur incontrôlée dans ce paramètre de requête device
circule directement vers l’assistant de condition if, qui utilise eval, comme vous pouvez le voir à la ligne 227, pour évaluer la condition de manière dynamique :
Les choses étant désormais plus claires, on comprend plus aisément comment plusieurs problèmes de sécurité peuvent s’additionner de manière imprévue :
dustjs-linkedin, un paquet open source, présente une faille de sécurité où les chaînes d’entrée sont mal nettoyées dans sa fonction
escapeHtml
.dustjs-helpers, un paquet open source, utilise une convention de codage non sécurisée comme la fonction
eval()
pour évaluer dynamiquement le code au moment de l’exécution.
Vous voulez voir comment j’ai exploité cette seule vulnérabilité pour pirater une véritable application en fonctionnement ? C’est juste en dessous !
Évitez aussi setTimeout()
et setInterval()
Avant d’en terminer avec ce conseil consistant à éviter eval()
, je souhaite également évoquer d’autres fonctions dont, en tant que développeur JavaScript, vous avez très certainement entendu parler ou que vous avez utilisées au moins une fois dans votre application : setTimeout()
et setInterval()
.
Peut-être ne savez-vous pas que ces fonctions reçoivent également des chaînes de code, comme dans l’exemple suivant :
setTimeout(“console.log(1+1)”, 1000);
Heureusement, les littéraux de chaîne ne sont pas autorisés dans un environnement Node.js !
2\. Éviter new Function()
Autre construction de langage similaire à eval()
, setTimeout()
et setInterval()
: le constructeur Function
qui permet de définir dynamiquement une fonction à partir de littéraux de chaîne.
Prenons l’exemple suivant :
const addition = new Function(‘a’, ‘b’, ‘return a+b’);
addition(1, 1)
Si je ne vous ai pas perdu en cours de route, vous connaissez déjà les problèmes de sécurité potentiels dus à la circulation de l’entrée utilisateur vers une telle fonction…
3\. Éviter la sérialisation de code dans JavaScript
La sérialisation tient une place à part dans l’écosystème Java. Mon ami Brian Vermeer a écrit un article de blog sur l’impact des vulnérabilités de sécurité sur les applications Java en raison d’opérations de sérialisation non sécurisées. Je ne peux que vous conseiller sa lecture : Sérialisation et désérialisation en Java : explication des vulnérabilités de la désérialisation Java.
Revenons à notre environnement JavaScript, où il semblerait qu’ici aussi la sérialisation fasse couler beaucoup d’encre.
Il est fort probable que vous ne codiez pas vous-même votre propre logique de sérialisation et de désérialisation. Mais entrez dans le monde merveilleux de npm et de ses plus de 1 500 000 paquets open source à votre disposition. Pourquoi ne pas les utiliser ?
js-yaml gagne en popularité avec plus de 28 000 000 de téléchargements par semaine et, d’après Snyk Advisor, il semble assez sain :
Ceci dit, vous pouvez voir sur la capture d’écran ci-dessus du paquet npm js-yaml que les versions précédentes contenaient des failles de sécurité. Lesquelles ?
Des versions de js-yaml se sont révélées vulnérables à l’exécution de code en raison de la désérialisation. La manière dont la faille se manifeste est due à l’utilisation suivante du constructeur new Function
() :
function resolveJavascriptFunction(object /*, explicit*/) {
/*jslint evil:true*/ var func;
try {
func = new Function('return ' + object);
return func();
} catch (error) {
return NIL;
}
}
Voyons à quoi ressemble un exploit de démonstration pour cette vulnérabilité :
var yaml = require('js-yaml');
x = "test: !!js/function > \n \
function f() { \n \
console.log(1); \n \
}();"
yaml.load(x);
Ainsi, si un acteur malveillant est capable de fournir une telle entrée, ou des parties de celle-ci, comme dans la variable x
dans le code de démonstration ci-dessus, alors cette vulnérabilité potentielle devient un réel danger.
La vulnérabilité ci-dessus remonte à 2013\. Toutefois, un rapport de vulnérabilité de sécurité de 2019 a trouvé un autre cas d’exécution de code arbitraire dans js-yaml. Nous vous recommandons la prudence, ou d’éviter purement et simplement new Function
() et d’analyser vos paquets open source tiers pour vous assurer que ces vulnérabilités ne sont pas présentes ou que, si elles le sont, vous pouvez les corriger automatiquement avec une requête d’extraction de correctif.
4\. Utiliser un linter de sécurité Node.js
C’est le moment de prendre nos outils, il est temps de parler des linters. Nous savons à quel point les développeurs JavaScript les apprécient. Que vous utilisiez standardjs ou eslint pour appliquer un style de code, ce sont des outils assez courants dans tout projet JavaScript ou Node.js.
Pourquoi ne pas appliquer de bonnes pratiques de sécurité ? C’est là que eslint-plugin-security entre en scène ! Comme le promettent les instructions README, l’utilisation du plugin est assez simple. Ajoutez simplement la configuration suivante du plugin eslint pour activer la configuration recommandée :
"plugins": [
"security"
],
"extends": [
"plugin:security/recommended"
Quelle est l’utilité du linter ?
Il contient des règles permettant de détecter les conventions de codage non sécurisées, telles que detect-eval-with-expression, qui détecte les utilisations de eval()
avec des expressions ou des littéraux de chaîne. Le linter contient aussi d’autres règles pour détecter par exemple l’utilisation des API Node.js child_process.
La dernière publication de eslint-plugin-security remonte à plus de quatre ans ; même s’il est possible qu’il fonctionne toujours correctement, il peut être pertinent d’envisager d’autres paquets pour lui succéder, comme eslint-plugin-security-node.
5\. Utiliser un outil d’analyse de code statique pour détecter et résoudre les problèmes d’injection de code
Les linters d’analyse de code statique (SCA) les plus élémentaires utilisés avec ESLint sont un bon point de départ. Ils fournissent suffisamment de contexte pour appliquer le style de code, mais comme nous l’avons vu avec le linter de sécurité Node.js, ils ne sont pas suffisamment flexibles pour résoudre les problèmes de sécurité.
Les développeurs nourrissent certaines inquiétudes quant aux linters de sécurité Node.js, notamment :
Les faux positifs : Les règles de linter étant assez élémentaires, elles peuvent alerter sur de nombreux faux positifs, engendrant frustration et confusion chez les développeurs. Par exemple, la règle
RegExp(matchEmailRegEx)
entraînera une erreur du linter de sécurité Node.js en raison de l’utilisation non littérale de la fonction RegExp.matchEmailRegEx
n’est-il qu’une constante dans mon fichier shared/variables.js ? Le linter n’est pas suffisamment intelligent pour le savoir.Des règles trop rigides : Suite logique au point ci-dessus : la règle est trop rigide. Soit vous utilisez
child_process.exec(someCommand, \[])
, soit vous ne l’utilisez pas. Le processus d’analyse de code statique tel qu’il est utilisé avec le linter n’est pas suffisamment intelligent pour comprendre quesomeCommand
est une constante que vous avez codée en dur. Le fait que vous utilisiez child_process.exec() avec un non littéral suffit à déclencher une erreur du linter, ce qui finit inévitablement par frustrer les développeurs, qui finissent par désactiver la règle.Des règles trop élémentaires : Il n’y a pas assez de règles et les résultats sont trop élémentaires. Tout ou rien, voilà le problème ! Il n’existe aucun contexte sur la façon dont les données circulent réellement d’une entrée utilisateur donnée vers un code sensible potentiel tel que l’exécution de commandes, des requêtes SQL, etc.
Comme nous l’avons déjà dit, un linter de sécurité comme eslint-plugin-security-node est un bon point de départ. C’est certainement mieux que rien du tout.
Toutefois, il existe de meilleures moyens pour détecter des problèmes de sécurité dans votre propre code, pendant que vous codez.
Laissez-moi vous présenter Snyk Code, un outil de test statique de la sécurité des applications (SAST), conçu pour les développeurs.
Détection d’une injection de commande dans une application node.js
Snyk Code sera bientôt publié. Voici en quelques mots les détails de son fonctionnement.
Tout d’abord, connectez-vous à Snyk avec un compte GitHub, puis importez le référentiel GitHub. Pour cela, cliquez sur Add project (Ajouter le projet), puis sur l’icône GitHub :
Puis, recherchez un référentiel dans la liste ou utilisez la zone de recherche pour en saisir le nom. Activez ensuite le référentiel pour lancer l’analyse :
Snyk importera ensuite le référentiel GitHub et le scannera rapidement.
Il détectera automatiquement d’autres fichiers de manifeste liés à des problèmes de sécurité potentiels : utilisation des dépendances open source avec des vulnérabilités connues, introduction de vulnérabilités de sécurité par votre image Docker, etc.
Attardons-nous sur notre code dans cette application Node.js en cliquant sur Code analysis (Analyse du code). Voici ce que nous trouvons :
Snyk Code a détecté plusieurs vulnérabilités. L’une d’entre elles est une vulnérabilité aux injections de commandes, comme nous le voyons ici :
Les failles de sécurité détectées dans cette ligne de code expliquent pourquoi la situation est problématique :
“Unsanitized input from the HTTP request body flows into child_process.exec, where it is used to build a shell command. This may result in a Command Injection vulnerability.”
Mais comment les données circulent-elles de ce paramètreurl
vers la fonction exec()
non sécurisée ? Cliquez sur le bouton Full details (Détails complets) pour afficher une version plus élaborée du flux de données et étayer le contexte :
Ici, nous voyons clairement comment Snyk Code a analysé la faille.
Le paramètre url
est créé à partir du tableau item
, qui est lui-même la source d’une entrée contrôlée par l’utilisateur, qui circule sous forme d’entrée de corps de message dans la variable req.body.content
.
Solution à l’injection de commande
Nous pouvons maintenant prendre d’autres mesures pour résoudre le problème de sécurité, telles que :
Plutôt que d’utiliser
exec()
, non sécurisée, nous pouvons utiliser la version sécurisée de cette API :execFile()
qui prend soin d’échapper les arguments qui lui sont fournis sous la forme d’un argument de fonction de tableau.Nous pouvons et devons valider, échapper ou nettoyer la variable de l’élément à partir de l’entrée de l’utilisateur avant qu’elle ne se transforme en code sensible tel que l’exécution de processus système.
En résumé
Merci et bravo d’avoir lu cet article jusqu’ici !
J’espère que vous êtes maintenant plus conscient des problèmes que les vulnérabilités d’injection de code peuvent engendrer, qu’elles proviennent de votre propre code ou de dépendances tierces que vous importez dans votre application.
Si cet article vous a été utile, les articles suivants, rédigés (en anglais) par mes collègues de Snyk, pourraient vous intéresser :
Si vous ou votre équipe développez en Go, consultez ce Guide de référence Go : 8 meilleures pratiques de sécurité pour les développeurs Go.
Si vous utilisez Java et Spring MVC en particulier, cet article pourrait vous intéresser : Solving Java security issues in my Spring MVC application (Résoudre les problèmes de sécurité Java dans mon application Spring MVC). Il traite également de la détection des problèmes de sécurité avec Snyk Code.
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.