Démonstration : injection SQL avec Node.js, Express et SQLite
Ce projet démontre comment une simple concaténation SQL peut rendre une application vulnérable à une injection SQL, et comment corriger cette faille à l'aide de requêtes préparées.
Il propose deux routes :
/demo-vulnerable/login→ version vulnérable/demo-secure/login→ version sécurisée
Installation
npm install
nodemon ./bin/www
L'application démarre sur :
http://localhost:3000
Objectifs pédagogiques
Cette mini-démonstration permet de :
-
comprendre ce qu'est une injection SQL
-
observer clairement la différence entre :
- une requête créée par concaténation (dangereux)
- une requête préparée avec paramètres (sécurisé)
-
manipuler Express, SQLite et les routes API
-
visualiser l'impact d'une exploitation sur une base de données
Structure du projet
project/
│── app.js
│── db/
│ └── database.js
│── routes/
│ ├── auth_vulnerable.js
│ └── auth_secure.js
└── bin/www
Comprendre l'architecture
1. Initialisation de la base en mémoire
const db = new sqlite3.Database(':memory:');
La base est volatile, recréée à chaque démarrage, et contient deux utilisateurs :
- admin / password123
- user / userpass
Diagramme — création de la base
flowchart TD
a["demarrage de l'application"] --> b["creation de la base en memoire"]
b --> c["creation de la table users"]
c --> d["insertion de l'utilisateur admin"]
c --> e["insertion de l'utilisateur user"]
2. Version vulnérable : concaténation SQL
Dans auth_vulnerable.js :
const sqlQuery = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
Cette méthode permet à un utilisateur d'injecter du code SQL.
Diagramme — route vulnérable
sequenceDiagram
participant client
participant serveur
participant sqlite
client->>serveur: envoi de donnees username et password
serveur->>serveur: creation d'une requete concatenee
serveur->>sqlite: execution de la requete non protegee
sqlite-->>serveur: renvoi d'une ligne ou non
serveur-->>client: reponse json
Tests de démonstration
Test 1 : attaque réussie (vulnérable)
Envoyer un POST :
POST http://localhost:3000/demo-vulnerable/login
Body JSON :
{
"username": "' OR 1=1 --",
"password": "peu-importe"
}
Résultat attendu :
Connexion acceptée, vous devenez admin même sans connaître le mot de passe.
Test 2 : l'attaque échoue (sécurisé)
Même requête mais vers :
POST http://localhost:3000/demo-secure/login
Body identique :
{
"username": "' OR 1=1 --",
"password": "peu-importe"
}
Résultat attendu :
Connexion refusée : l'injection est traitée comme du texte.
3. Version sécurisée : requêtes préparées
Dans auth_secure.js :
const sqlQuery = "SELECT * FROM users WHERE username = ? AND password = ?";
const params = [username, password];
db.get(sqlQuery, params, (...));
SQLite protège automatiquement les valeurs et empêche l'injection.
Diagramme — route sécurisée
flowchart TD
a["reception de la requete http"] --> b["construction de la requete preparee"]
b --> c["envoi de la requete avec parametres"]
c --> d["sqlite assainit les valeurs"]
d --> e["execution de la requete securisee"]
Comprendre la faille SQL
Exemple vulnérable
Requête générée :
SELECT * FROM users WHERE username = '' OR 1=1 --' AND password='peu-importe'
OR 1=1 rend la condition toujours vraie.
Bonnes pratiques
- toujours utiliser des requêtes préparées
- jamais concaténer des valeurs utilisateur dans du SQL
- toujours valider / nettoyer les entrées
- activer des logs SQL en développement
- éviter les messages d'erreurs trop détaillés en production