You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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 :

  1. Créer un compte avec mot de passe "test123"
  2. Ouvrir la base SQLite : sqlite3 data/bricoloc.db
  3. Exécuter : SELECT email, password FROM users;
  4. 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 :

  1. Se connecter sur le site
  2. 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>
  1. Ouvrir cette page dans le même navigateur
  2. 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 :

  1. Se connecter avec un compte utilisateur standard
  2. Modifier dans DevTools : document.cookie = "userId=1" (si l'admin est ID=1)
  3. Ou modifier directement la DB : UPDATE users SET is_admin=1 WHERE id=2
  4. 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 :

  1. Aller sur /admin/outils
  2. Créer un outil avec un fichier .php ou .exe
  3. Le fichier est accepté et stocké dans /uploads
  4. 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 :

  1. Aller sur /outils/1/reserver
  2. Sélectionner des dates (ex: 5 jours, prix = 250€)
  3. Ouvrir DevTools Console
  4. Exécuter : document.getElementById('prix_total').value = '0.01'
  5. Soumettre le formulaire
  6. 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 :

  1. Ouvrir 2 onglets du site
  2. Dans les 2 onglets, aller sur le même outil avec 1 seul stock disponible
  3. Soumettre les 2 formulaires en même temps (clic simultané)
  4. 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 :

  1. Créer une réservation du 1er au 5 décembre
  2. Créer une 2ème réservation du 3 au 7 décembre (même outil, même entrepôt)
  3. 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 :

  1. Réserver un outil (stock passe de 5 à 4)
  2. Annuler la réservation
  3. 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é)

  1. Phase 1 - Sécurité critique (2 semaines)

    • BUG-001, 002, 003, 004, 007
  2. Phase 2 - Logique métier (1 semaine)

    • BUG-008, 009, 010
  3. Phase 3 - Architecture (4 semaines)

    • Refactoring complet (services, controllers, middlewares)
    • BUG-015, 016, 017, 018
  4. 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