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
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 ✅
|