15 KiB
🐛 Liste des Bugs Intentionnels - BricoLoc Legacy
Application : BricoLoc Legacy (Réplique monolithique)
Objectif : Démontrer les problématiques d'une architecture legacy mal maintenue
Statut : Bugs intentionnels pour démonstration pédagogique
📊 Résumé des Bugs
| Catégorie | Nombre | Impact Critique | Impact Élevé | Impact Moyen |
|---|---|---|---|---|
| Sécurité | 7 | 5 | 2 | 0 |
| Validation | 2 | 1 | 1 | 0 |
| Logique Métier | 4 | 3 | 1 | 0 |
| Performance | 2 | 0 | 2 | 0 |
| UX | 2 | 0 | 0 | 2 |
| Code Quality | 3 | 2 | 1 | 0 |
| TOTAL | 20 | 11 | 7 | 2 |
🔴 Bugs Critiques (Impact Sécurité/Métier)
BUG-001 : Mots de passe stockés en clair
Catégorie : Sécurité 🔴 CRITIQUE
Fichier : src/server.js (ligne ~90)
Description : Les mots de passe sont stockés en texte brut dans la base de données sans aucun hachage.
Code problématique :
await run(
`INSERT INTO users (email, password, nom, prenom, ...)
VALUES (?, ?, ?, ?, ...)`,
[email, password, nom, prenom, ...] // ❌ Mot de passe en clair
)
Reproduction :
- Créer un compte avec mot de passe "test123"
- Ouvrir la base SQLite :
sqlite3 data/bricoloc.db - Exécuter :
SELECT email, password FROM users; - Le mot de passe "test123" est visible en clair
Impact :
- Violation RGPD
- Si la DB est compromise, tous les mots de passe sont exposés
- Les utilisateurs réutilisant leurs mots de passe sont vulnérables
Correction attendue :
const bcrypt = require('bcrypt');
const hashedPassword = await bcrypt.hash(password, 10);
await run(`INSERT INTO users (..., password, ...) VALUES (..., ?, ...)`, [..., hashedPassword, ...]);
BUG-002 : Absence de protection CSRF
Catégorie : Sécurité 🔴 CRITIQUE
Fichier : src/server.js (configuration middleware)
Description : Aucun token CSRF n'est généré ni vérifié sur les formulaires.
Code problématique :
// Configuration sessions
app.use(session({
secret: 'bricoloc-secret-key', // ❌ Aussi : secret en dur
// ❌ Pas de middleware CSRF
}))
Reproduction :
- Se connecter sur le site
- Créer une page HTML malveillante :
<form action="http://localhost:3000/reservations" method="POST">
<input type="hidden" name="outil_id" value="1">
<input type="hidden" name="prix_total" value="0.01">
<!-- ... autres champs ... -->
</form>
<script>document.forms[0].submit();</script>
- Ouvrir cette page dans le même navigateur
- La réservation est créée sans consentement
Impact : Attaque CSRF permettant des actions non autorisées
Correction attendue :
const csrf = require('csurf');
app.use(csrf());
BUG-003 : Injections SQL multiples
Catégorie : Sécurité 🔴 CRITIQUE
Fichiers : src/server.js (routes /outils, /recherche, /admin/reservations, /admin/stocks)
Description : Concaténation directe de paramètres utilisateur dans les requêtes SQL.
Code problématique :
// Route /outils
if (categorie) {
sql += ` AND o.categorie_id = ${categorie}` // ❌ SQL Injection
}
// Route /recherche
const sql = `SELECT * FROM outils WHERE nom LIKE '%${q}%'` // ❌ SQL Injection
Reproduction :
# Injection dans la recherche
curl "http://localhost:3000/recherche?q=test' OR '1'='1"
# Injection dans les filtres
curl "http://localhost:3000/outils?categorie=1' OR '1'='1"
Impact :
- Extraction complète de la base de données
- Modification/suppression de données
- Élévation de privilèges possible
Correction attendue :
// Utiliser des paramètres préparés
const params = [];
if (categorie) {
sql += ` AND o.categorie_id = ?`;
params.push(categorie);
}
const outils = await query(sql, params);
BUG-004 : Absence de vérification des rôles admin
Catégorie : Sécurité 🔴 CRITIQUE
Fichier : src/server.js (toutes les routes /admin/*)
Description : Pas de middleware requireAdmin, vérification manuelle is_admin répétée.
Code problématique :
app.get('/admin', requireAuth, async (req, res) => {
// ❌ Vérification manuelle dans chaque route
if (!res.locals.user.is_admin) {
return res.redirect('/')
}
// ...
})
Reproduction :
- Se connecter avec un compte utilisateur standard
- Modifier dans DevTools :
document.cookie = "userId=1"(si l'admin est ID=1) - Ou modifier directement la DB :
UPDATE users SET is_admin=1 WHERE id=2 - Accéder à
/admin
Impact : N'importe quel utilisateur peut devenir admin
Correction attendue :
function requireAdmin(req, res, next) {
if (!req.session.userId) return res.redirect('/login');
if (!res.locals.user || !res.locals.user.is_admin) {
return res.status(403).send('Accès refusé');
}
next();
}
app.get('/admin', requireAdmin, async (req, res) => { ... });
BUG-005 : Upload fichiers non sécurisé
Catégorie : Sécurité 🟠 ÉLEVÉ
Fichier : src/server.js (configuration multer)
Description : Aucune validation du type MIME ni de l'extension des fichiers uploadés.
Code problématique :
const upload = multer({
storage: storage
// ❌ Pas de fileFilter, pas de limite de taille
})
Reproduction :
- Aller sur
/admin/outils - Créer un outil avec un fichier
.phpou.exe - Le fichier est accepté et stocké dans
/uploads - Potentiellement exécutable si le serveur est mal configuré
Impact : Upload de fichiers malveillants (webshell, virus)
Correction attendue :
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max
fileFilter: (req, file, cb) => {
if (!file.mimetype.startsWith('image/')) {
return cb(new Error('Seulement les images sont autorisées'));
}
cb(null, true);
}
});
BUG-007 : Prix calculé côté client
Catégorie : Validation 🔴 CRITIQUE
Fichiers : src/views/nouvelle-reservation.ejs, src/public/js/main.js
Description : Le prix total de la réservation est calculé en JavaScript côté client et envoyé au serveur.
Code problématique :
// Client-side (manipulable via DevTools)
const prixTotal = (prixJour * diffDays).toFixed(2);
document.getElementById('prix_total').value = prixTotal; // ❌ Envoyé tel quel au serveur
Reproduction :
- Aller sur
/outils/1/reserver - Sélectionner des dates (ex: 5 jours, prix = 250€)
- Ouvrir DevTools Console
- Exécuter :
document.getElementById('prix_total').value = '0.01' - Soumettre le formulaire
- Réservation créée pour 0.01€
Impact : Perte financière importante, fraude
Correction attendue :
// Serveur (server.js)
app.post('/reservations', requireAuth, async (req, res) => {
const { outil_id, date_debut, date_fin } = req.body;
// Recalculer le prix côté serveur
const outil = await get('SELECT prix_jour FROM outils WHERE id = ?', [outil_id]);
const nbJours = calculerNbJours(date_debut, date_fin);
const prixTotal = outil.prix_jour * nbJours;
await run(`INSERT INTO reservations (..., prix_total) VALUES (..., ?)`, [..., prixTotal]);
});
BUG-008 : Race condition sur les réservations
Catégorie : Logique Métier 🔴 CRITIQUE
Fichier : src/server.js (route POST /reservations)
Description : Pas de transaction SQL lors de la création d'une réservation.
Code problématique :
// Vérification stock
const stock = await get('SELECT quantite_disponible FROM stocks WHERE ...');
if (stock.quantite_disponible <= 0) { ... }
// ❌ Pas de transaction, un autre utilisateur peut réserver entre-temps
// Créer réservation
await run('INSERT INTO reservations ...');
// Décrémenter stock
await run('UPDATE stocks SET quantite_disponible = quantite_disponible - 1 ...');
Reproduction :
- Ouvrir 2 onglets du site
- Dans les 2 onglets, aller sur le même outil avec 1 seul stock disponible
- Soumettre les 2 formulaires en même temps (clic simultané)
- Les 2 réservations sont créées (double-booking)
Impact : Surbooking, promesses non tenues
Correction attendue :
await db.run('BEGIN TRANSACTION');
try {
const stock = await get('SELECT quantite_disponible FROM stocks WHERE ... FOR UPDATE');
if (stock.quantite_disponible <= 0) throw new Error('Stock insuffisant');
await run('INSERT INTO reservations ...');
await run('UPDATE stocks SET quantite_disponible = quantite_disponible - 1 ...');
await db.run('COMMIT');
} catch (err) {
await db.run('ROLLBACK');
throw err;
}
BUG-009 : Vérification de disponibilité incomplète
Catégorie : Logique Métier 🔴 CRITIQUE
Fichier : src/server.js (route POST /reservations)
Description : Ne vérifie pas les conflits de dates avec les réservations existantes.
Code problématique :
// Vérifie seulement le stock global
const stock = await get('SELECT quantite_disponible FROM stocks WHERE ...');
// ❌ Ne vérifie PAS si une autre réservation existe pour ces dates
Reproduction :
- Créer une réservation du 1er au 5 décembre
- Créer une 2ème réservation du 3 au 7 décembre (même outil, même entrepôt)
- Les 2 réservations sont acceptées (chevauchement)
Impact : Double-booking, conflit de disponibilité
Correction attendue :
const conflits = await query(`
SELECT COUNT(*) as count FROM reservations
WHERE outil_id = ? AND entrepot_id = ?
AND statut IN ('en_attente', 'confirmee')
AND (
(date_debut <= ? AND date_fin >= ?) OR
(date_debut <= ? AND date_fin >= ?) OR
(date_debut >= ? AND date_fin <= ?)
)
`, [outil_id, entrepot_id, date_debut, date_debut, date_fin, date_fin, date_debut, date_fin]);
if (conflits.count > 0) {
throw new Error('Dates non disponibles');
}
BUG-010 : Annulation ne libère pas le stock
Catégorie : Logique Métier 🟠 ÉLEVÉ
Fichier : src/server.js (route POST /reservations/:id/annuler)
Description : Lors de l'annulation, le stock n'est pas remis à jour.
Code problématique :
app.post('/reservations/:id/annuler', requireAuth, async (req, res) => {
await run('UPDATE reservations SET statut = ? WHERE id = ?', ['annulee', reservationId]);
// ❌ BUG : Ne remet pas à jour stocks.quantite_disponible
// Devrait faire : UPDATE stocks SET quantite_disponible = quantite_disponible + 1
});
Reproduction :
- Réserver un outil (stock passe de 5 à 4)
- Annuler la réservation
- Le stock reste à 4 au lieu de revenir à 5
Impact : Incohérence des stocks, perte de disponibilité
🟠 Bugs Élevés (Performance/Architecture)
BUG-011 : Absence de pagination serveur
Catégorie : Performance 🟠 ÉLEVÉ
Fichier : src/server.js (route /outils)
Description : Tous les outils sont chargés en mémoire sans LIMIT ni pagination.
Code problématique :
const outils = await query(sql); // ❌ Pas de LIMIT
// Charge 1000+ outils en mémoire
Impact : Lenteur avec beaucoup de données, consommation mémoire
BUG-012 : Requêtes N+1
Catégorie : Performance 🟠 ÉLEVÉ
Fichier : src/server.js (routes /outils, /mes-reservations)
Description : Boucles avec requêtes SQL imbriquées.
Code problématique :
const outils = await query('SELECT * FROM outils');
for (const outil of outils) {
const stocks = await query('SELECT * FROM stocks WHERE outil_id = ?', [outil.id]); // ❌ N requêtes
outil.stocks = stocks;
}
Impact : 1 + N requêtes au lieu d'1 seule requête avec JOIN
BUG-013 : Messages d'erreur trop détaillés
Catégorie : UX/Sécurité 🟡 MOYEN
Fichier : src/server.js (route POST /login)
Description : Les messages révèlent si un email existe dans la base.
Code problématique :
if (!user) {
req.flash('error', 'Aucun compte trouvé avec cette adresse email'); // ❌ Révèle l'existence
return res.redirect('/login');
}
if (user.password !== password) {
req.flash('error', 'Mot de passe incorrect'); // ❌ Révèle que l'email existe
return res.redirect('/login');
}
Impact : Énumération d'emails, fuite d'informations
BUG-014 : Problèmes de timezone
Catégorie : UX 🟡 MOYEN
Fichiers : src/views/reservations.ejs, src/views/nouvelle-reservation.ejs
Description : Les dates sont affichées/manipulées sans gestion cohérente des timezones.
Code problématique :
new Date(reservation.date_debut).toLocaleDateString('fr-FR') // ❌ Décalage possible
Impact : Dates incorrectes d'un jour selon le timezone
🟣 Bugs Architecturaux
BUG-015 : Code dupliqué partout
Catégorie : Code Quality 🔴 CRITIQUE
Fichier : src/server.js (tout le fichier)
Description : Même logique répétée (vérification admin, requêtes SQL, etc.).
Impact : Maintenance difficile, risque d'oubli lors de modifications
BUG-016 : Pas de gestion d'erreurs
Catégorie : Code Quality 🟠 ÉLEVÉ
Fichier : src/server.js, src/public/js/main.js
Description : Pas de try/catch global, erreurs non gérées.
Impact : Crash de l'app en production
BUG-017 : Logique métier dans les vues
Catégorie : Architecture 🔴 CRITIQUE
Fichiers : src/views/outils.ejs, src/views/reservations.ejs
Description : Calculs et logique dans les templates EJS.
Code problématique :
<% outil.total_disponible = stocks.reduce((sum, s) => sum + s.quantite_disponible, 0) %>
Impact : Impossible à tester, difficile à maintenir
BUG-018 : Monolithe de 780+ lignes
Catégorie : Architecture 🔴 CRITIQUE
Fichier : src/server.js
Description : Tout le code dans un seul fichier.
Impact : Impossible à maintenir, conflits Git constants
📈 Statistiques Finales
- Total bugs identifiés : 20
- Fichiers affectés : 8
- Lignes de code vulnérables : ~200
- Score de sécurité estimé : 15/100 ⚠️
- Dette technique : ~40 jours/homme de refactoring
🎯 Plan de Correction (Non implémenté)
-
Phase 1 - Sécurité critique (2 semaines)
- BUG-001, 002, 003, 004, 007
-
Phase 2 - Logique métier (1 semaine)
- BUG-008, 009, 010
-
Phase 3 - Architecture (4 semaines)
- Refactoring complet (services, controllers, middlewares)
- BUG-015, 016, 017, 018
-
Phase 4 - Performance & UX (1 semaine)
- BUG-011, 012, 013, 014
OU : Migrer vers l'application moderne (recommandé)
Dernière mise à jour : 17 Novembre 2025
Statut : Documentation complète ✅