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.

526 lines
15 KiB
Markdown

# 🐛 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** :
```javascript
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** :
```javascript
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** :
```javascript
// 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 :
```html
<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>
```
3. Ouvrir cette page dans le même navigateur
4. La réservation est créée sans consentement
**Impact** : Attaque CSRF permettant des actions non autorisées
**Correction attendue** :
```javascript
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** :
```javascript
// 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** :
```bash
# 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** :
```javascript
// 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** :
```javascript
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** :
```javascript
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** :
```javascript
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** :
```javascript
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** :
```javascript
// 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** :
```javascript
// 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** :
```javascript
// 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** :
```javascript
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** :
```javascript
// 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** :
```javascript
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** :
```javascript
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** :
```javascript
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** :
```javascript
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** :
```javascript
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** :
```javascript
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** :
```ejs
<% 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 ✅