Skip to main content

Prévenir les attaques par injection SQL dans Node.js

Écrit par
Headshot of Lucien Chemaly

Lucien Chemaly

20 février 2024

0 minutes de lecture

À mesure que notre dépendance aux systèmes logiciels s’accroît, les menaces de sécurité se multiplient. L’injection SQL est une menace notable pour les développeurs, en particulier ceux qui travaillent avec Node.js.

L’injection SQL est une attaque malveillante qui consiste à injecter du code SQL dans un système, ce qui expose des informations sensibles, corrompt ou supprime des données et peut parfois permettre aux attaquants d’obtenir un accès non autorisé. Il est difficile de contrer cette menace, ce qui souligne la nécessité de sécuriser vos applications Node.js.

Dans cet article, découvrez pourquoi les attaques par injection SQL représentent une menace importante et comment protéger vos applications Node.js contre celles-ci.

Comprendre l’injection SQL dans les applications Node.js

Les attaques par injection SQL surviennent principalement lorsqu’une application Node.js gère mal les entrées utilisateur. Lorsqu’une entrée utilisateur est intégrée à une requête SQL sans validation, assainissement ni construction appropriés de la requête, elle crée une faille que les attaquants peuvent exploiter pour manipuler la requête, entraînant un accès non autorisé ou une fuite de données. Pour consulter les vulnérabilités passées et récentes de Node.js, la Snyk Vulnerability Database en répertorie une liste.

Application Express Node.js vulnérable aux injections SQL

Pour mieux comprendre le fonctionnement des injections SQL, créons rapidement une application vulnérable avec Node.js, Express et une base de données PostgreSQL. L’application récupère les entrées utilisateur d’un formulaire, construit une requête SQL, puis l’exécute sur la base de données pour récupérer des données.

Commencez par télécharger et installer Node.js sur votre machine. Node.js est l’environnement d’exécution qui exécute votre code JavaScript côté serveur.

Créez ensuite un nouveau répertoire pour votre projet (c.-à-d. vulnerable-app) et accédez-y dans votre terminal à l’aide de la commande cd vulnerable-app.

Dans le répertoire de votre projet, exécutez la commande npm init -y. Elle crée un fichier package.json avec des valeurs par défaut, qui sert de fichier manifeste pour votre projet Node.js.

Installez Express et la bibliothèque pg en exécutant npm install express pg. Express est un framework léger pour applications Web, et pg est un client Node.js pour PostgreSQL.

Téléchargez et installez ensuite PostgreSQL. Une fois l’installation terminée, créez une base de données nommée vulnerable_db. Dans cette base, créez une table nommée users avec une colonne nommée name. Voici la commande SQL à utiliser :

   CREATE TABLE users (
       id SERIAL PRIMARY KEY,
       name VARCHAR(255)
   );

Pour en savoir plus sur l’installation et l’exécution de PostgreSQL, consultez cette documentation W3Schools.

Créez un fichier nommé app.js et ajoutez-y le code suivant :

const express = require("express");
const { Client } = require("pg");
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json()); // To handle JSON input
const client = new Client({
  host: "your-host",
  database: "your-database",
  username: "your-username",
  password: "your-password",
  port: "your-port",
});
client.connect();
app.post("/search", (req, res) => {
  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = '" + userInput + "';";
  client.query(query, (err, result) => {
    if (err) throw err;
    res.send(result.rows);
  });
});
app.post("/create", (req, res) => {
  const { name } = req.body;
  if (!name) {
    res.status(400).send("Name is required");
    return;
  }
  const query = "INSERT INTO users (name) VALUES ($1) RETURNING *;";
  client.query(query, [name], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).send("Internal Server Error");
      return;
    }
    res.send(result.rows[0]); // Respond with the created user record
  });
});
app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

Ce code initialise un serveur Express et configure des connexions à une base de données PostgreSQL à l’aide de la bibliothèque pg. Il configure le serveur pour gérer les entrées encodées dans l’URL et au format JSON.

Deux routes sont définies : une route POST /search et une route POST /create. La route /search récupère les entrées utilisateur du corps de la requête, construit une requête SQL et l’exécute sur la base de données, puis renvoie le résultat. Toutefois, elle présente une vulnérabilité d’injection SQL, car les entrées utilisateur sont concaténées à la requête. La route /create extrait une valeur name du corps de la requête, vérifie qu’elle est présente, puis construit une requête SQL paramétrée sécurisée afin d’insérer un nouvel utilisateur dans la base de données et renvoie l’enregistrement créé. Enfin, le serveur commence à écouter sur le port 3000 et indique qu’il est prêt dans un message de la console.

Remarque : Veillez à remplacer your-username, your-password, your-database, your-host et your-port par les paramètres que vous avez configurés pour PostgreSQL.

En l’absence de validation et d’assainissement des entrées, le code est vulnérable aux injections SQL. Un attaquant peut saisir une valeur comme ' OR '1'='1'; -- dans le formulaire. Cette entrée met fin à la requête d’origine et la modifie ainsi : SELECT * FROM users WHERE name = '' OR '1'='1'; --';. La requête renvoie alors tous les enregistrements de la table users, car '1'='1' est toujours vrai. C’est un exemple classique d’injection SQL par concaténation de chaînes.

Maintenant que tout est configuré, vous pouvez démarrer votre serveur. Exécutez la commande node app.js dans le terminal. Votre serveur démarre et le message Server is running on port 3000 s’affiche pour confirmer son bon fonctionnement :

Terminal affichant une application Node.js lancée avec node app.js et le message « Server is running on port 3000. »

Pour tester l’application, créez quelques utilisateurs depuis votre terminal ou votre interpréteur de commandes :

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User1"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User2"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User3"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User4"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User5"}'

Ensuite, pour rechercher un utilisateur en vue de vérifier son identité, de mettre à jour ses données ou d’effectuer un audit et une surveillance, envoyez une requête de recherche sur ses données. Exécutez la commande suivante :

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d '{"userInput":"User1"}'

Le résultat devrait ressembler à ceci :

[{"id":1,"name":"User1"}]

Ajoutez maintenant une injection SQL pour récupérer tous les utilisateurs à l’aide de la commande suivante :

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d "{\"userInput\":\"' OR '1'='1'; -- \"}"

Le résultat devrait ressembler à ceci :

[{"id":1,"name":"User1"},{"id":2,"name":"User2"},{"id":3,"name":"User3"},{"id":4,"name":"User4"},{"id":5,"name":"User5"},{"id":6,"name":"User2"},{"id":7,"name":"User2"},{"id":8,"name":"User3"},{"id":9,"name":"User4"},{"id":10,"name":"User5"}]

Dans cette commande, les données JSON sont entourées de guillemets doubles (" ") afin de former une chaîne JSON complète, tandis que les guillemets à l’intérieur des données JSON sont échappés à l’aide d’une barre oblique inverse (\) pour qu’ils soient correctement inclus dans la chaîne JSON. La valeur de userInput est définie sur ' OR '1'='1'; -- . Comme expliqué précédemment, elle manipule la requête SQL pour renvoyer tous les enregistrements utilisateur en modifiant la condition afin qu’elle soit toujours vraie.

Comment prévenir les vulnérabilités d’injection SQL dans Node.js

Maintenant que vous savez à quel point les attaques par injection SQL peuvent se produire facilement, découvrez différentes façons de protéger votre application et de la rendre robuste et sûre pour vos utilisateurs.

Utiliser des paramètres de substitution dans les requêtes

Pour prévenir les injections SQL, il est essentiel d’éviter de construire les requêtes SQL par concaténation de chaînes. Les attaquants peuvent manipuler les requêtes en injectant du code SQL malveillant dans les champs de saisie. Utilisez plutôt des paramètres de substitution pour que les entrées utilisateur soient traitées comme des données et non comme du code exécutable :

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    client.query(query, [userInput], (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

Utiliser des instructions préparées

L’une des méthodes les plus efficaces pour prévenir les attaques par injection SQL consiste à utiliser des instructions préparées. Celles-ci garantissent que les entrées utilisateur sont toujours traitées comme des données et non comme du code exécutable.

Voici comment modifier votre route /search pour utiliser des instructions préparées avec la bibliothèque pg :

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    const preparedQuery = {
        text: query,
        values: [userInput]
    };
    client.query(preparedQuery, (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

Dans ce code, l’instruction préparée est définie sous forme d’objet comportant une propriété text pour la requête SQL et une propriété values pour les entrées utilisateur. Cette séparation vous permet de définir une seule fois le modèle de requête SQL et de fournir différentes valeurs pour le paramètre de substitution à chaque exécution de l’instruction préparée. Il s’agit d’une méthode plus sûre et plus efficace pour gérer les entrées utilisateur dans les requêtes SQL.

Valider et assainir les entrées utilisateur

La vérification des entrées utilisateur est essentielle pour garantir qu’elles respectent des exigences précises, comme un format, une longueur ou une plage de valeurs corrects. Des bibliothèques de validation comme joi ou express-validator peuvent simplifier cette tâche. Elles fournissent des règles et des fonctions de validation prédéfinies qui facilitent l’application des exigences relatives aux entrées et l’automatisation du processus de validation.

De plus, il est essentiel d’assainir les entrées en supprimant ou en échappant les caractères potentiellement dangereux pour maintenir un environnement applicatif sécurisé et robuste. L’assainissement des entrées consiste à nettoyer les entrées utilisateur afin qu’elles ne causent aucun dommage ni comportement inattendu lors de leur traitement par l’application.

Voici comment mettre en œuvre la validation et l’assainissement à l’aide de la bibliothèque express-validator :

 const { body, validationResult } = require('express-validator');

app.post("/search", [
  body('userInput').trim().escape().isAlphanumeric(),
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = $1;";

  client.query(query, [userInput], (err, result) => {
      if (err) throw err;
      res.send(result.rows);
  });
});

Utiliser les dernières versions des bibliothèques ORM et SQL

Veillez à toujours utiliser les dernières versions des bibliothèques ORM et SQL. Leurs responsables publient régulièrement des mises à jour et des correctifs pour résoudre les vulnérabilités de sécurité connues :

npm install pg@latest

Examiner régulièrement le code et réaliser des audits de sécurité

Effectuez régulièrement des revues de code et des audits de sécurité pour détecter les vulnérabilités au plus tôt. Utilisez des outils comme npm audit ou snyk pour automatiser la recherche de vulnérabilités connues dans vos dépendances.

Utiliser l’extension IDE Snyk pour Visual Studio Code afin de détecter les vulnérabilités

Snyk est un outil de sécurité conçu pour les développeurs, qui les aide à détecter et à corriger les vulnérabilités dans leur code et leurs dépendances open source. Il s’intègre parfaitement au workflow de développement et surveille et analyse en continu les failles de sécurité, notamment celles liées aux injections SQL.

Snyk ne se contente pas de détecter les vulnérabilités : il propose également des pull requests de correction automatisées, pour faciliter la résolution et la prévention des problèmes de sécurité potentiels. La plateforme s’intègre à différents environnements de développement comme GitHub, GitLab et Bitbucket, et propose une extension dédiée pour Visual Studio Code qui fournit un retour en temps réel pendant le développement.

Configurer une extension Snyk

La configuration d’une extension Snyk est simple. Commencez par télécharger et installer Visual Studio Code, si ce n’est pas déjà fait. Une fois l’installation terminée, lancez Visual Studio Code et ouvrez la vue Extensions en appuyant sur Ctrl + Shift + X sous Windows ou sur Command + Shift + X sous macOS.

Dans la vue Extensions, saisissez « Snyk » dans la barre de recherche, puis cliquez sur le bouton Install de l’extension Snyk :

Marketplace de Visual Studio Code affichant une recherche de « snyk » et l’extension Snyk Security avec des boutons Installer.

Après l’installation, l’extension Snyk devrait apparaître dans la barre de navigation à gauche de Visual Studio Code. Cliquez dessus, puis sur Trust workspace and connect. Vous devrez vous connecter à votre compte Snyk pour utiliser l’extension. Si vous n’avez pas de compte Snyk, vous pouvez en créer un gratuitement :

Visual Studio Code affichant l’extension Snyk, avec le bouton « Faites confiance à l’espace de travail et connectez-vous » mis en évidence, ainsi que des détails sur l’analyse des vulnérabilités.

Vous serez ensuite invité à vous connecter et à vous authentifier. Une fois la procédure terminée, le message Authenticated devrait s’afficher :

Page de confirmation d’authentification Snyk affichant le message « Authentifié » et « Votre compte a été authentifié. Snyk est maintenant prêt à être utilisé. »

Une fois l’extension Snyk installée et configurée, elle analyse automatiquement votre projet à la recherche de vulnérabilités connues, notamment de risques potentiels d’injection SQL.

Accédez à votre compte sur la plateforme Snyk et suivez les instructions pour activer Snyk Code. Cette activation est essentielle pour permettre à Snyk de détecter les vulnérabilités dans votre code, comme les injections SQL.

Si l’option Snyk Code n’est pas activée sur votre compte Snyk, l’extension Snyk pour Visual Studio Code ne détectera pas les injections SQL liées à de mauvaises pratiques de codage. Elle détectera uniquement les vulnérabilités liées aux bibliothèques obsolètes, entre autres.

Snyk ne se contente pas de détecter les vulnérabilités : il propose aussi des conseils et des ressources pour les corriger. Cette fonctionnalité peut s’avérer précieuse, surtout lorsque vous travaillez sur de grandes bases de code ou dans des délais serrés.

Tout le code de ce tutoriel est disponible dans le dépôt GitHub suivant.

Conclusion

Dans cet article, vous avez découvert comment les injections SQL se manifestent dans les applications Node.js et plusieurs stratégies pour les prévenir. En mettant à jour vos bibliothèques ORM et SQL, en assainissant les entrées utilisateur, en utilisant des paramètres de substitution dans les requêtes et en tirant parti de l’extension IDE Snyk pour Visual Studio Code, vous disposez de nombreuses solutions pour protéger vos applications Node.js contre les attaques par injection SQL.

L’extension Snyk vous aide non seulement à détecter les vulnérabilités d’injection SQL, mais aussi à les corriger, en jouant le rôle de mentor virtuel en sécurité. En intégrant Snyk à votre workflow de développement, vous ne vous contentez pas de réagir aux menaces : vous les prévenez de manière proactive et veillez à ce que vos applications restent robustes et sécurisées avant leur mise en production.

Dans votre parcours de développement Node.js, gardez à l’esprit que la sécurité n’est pas une tâche ponctuelle, mais un processus continu. Tenez-vous au courant des dernières bonnes pratiques de sécurité, continuez à apprendre et veillez à protéger vos applications. Vos utilisateurs vous en remercieront, et vous aurez l’esprit tranquille en sachant que vos données et celles de vos utilisateurs sont en sécurité.

Lancez-vous dans le Capture the Flag

Découvrez comment résoudre des défis Capture the Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

blog feature ai lilac
Blog

Transformez votre backlog de vulnérabilités en pull requests grâce à la remédiation agentique

Evo Remediation Agent est désormais disponible en aperçu ouvert et apporte la résolution automatisée des dépendances à Evo Agentic AppSec. Les équipes AppSec peuvent désormais lancer des campagnes de remédiation continues

AI Background min crop
Blog

L’IA fait surgir tous les risques que vous avez acceptés : sept enseignements du panel de sécurité de l’IA sans filtre

L’IA rend les attaques moins coûteuses et plus rapides, exposant des risques déjà présents dans les organisations. Quatre responsables de la sécurité expliquent pourquoi une défense à la vitesse des machines et une remédiation accélérée sont désormais indispensables.

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser la build ?

VulnBump évalue des agents IA sur 307 mises à niveau réelles de dépendances. Une correction n’est validée que si la vulnérabilité disparaît et que la build et les tests restent au vert.