Aller au contenu
Retour aux projets
Personnel — En cours

Outil d'évaluation de vulnérabilités (Python + CVSS)

Outil d'évaluation de vulnérabilités (Python + CVSS)

CLI Python pour des audits de vulnérabilités structurés avec scoring CVSS v3.1 et génération de rapports HTML

Contexte

Le plan initial prévoyait un rapport basé sur un scan Nessus simulé contre un réseau fictif. En pratique, j'ai construit mon propre scanner plutôt que de rejouer la sortie d'un outil existant — un signal plus fort pour un poste SOC/Pentest/AppSec : la capacité à concevoir et coder un outil d'évaluation de vulnérabilités (architecture des checks, moteur de scoring CVSS, génération de rapport), pas seulement à lire la sortie de Nessus.

Méthodologie — 3 familles de checks

  • Network : scan de ports concurrent (ThreadPoolExecutor, 1-1024), détection de services à risque (SMB/445, Telnet/23, Redis/6379 sans auth, RDP/3389), tentative de zone transfer DNS (AXFR)
  • System : inspecte la machine qui exécute l'outil (pas la cible) — version OS, configuration SSH (sshd_config), fichiers world-writable dans /tmp
  • Web : headers de sécurité manquants (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy), validité du certificat TLS

Le moteur CVSS v3.1

Chaque finding porte un vecteur CVSS (vecteur d'attaque, complexité, privilèges requis, interaction utilisateur, impact confidentialité/intégrité/disponibilité). Le score n'est pas une constante choisie à la main : il est calculé via la formule officielle du standard (Exploitability × Impact, algorithme Roundup du spec CVSS). Exemple réel — un SMB exposé sans authentification calcule CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 (Critical). La sévérité est ensuite dérivée du score calculé, pas assignée séparément, ce qui élimine tout risque d'incohérence entre score et sévérité affichée.

Bugs trouvés et corrigés pendant la revue de code

  • Check TLS jamais déclenché : getpeercert() retourne {} quand verify_mode=CERT_NONE, donc la détection de certificat expiré ne fonctionnait jamais, silencieusement. Corrigé par une lecture du certificat en DER (binary_form=True) + parsing via cryptography — vérifié contre expired.badssl.com, qui détecte bien un certificat expiré depuis 2015
  • Scan de ports séquentiel : 1024 ports × 0,5 s de timeout, jusqu'à 8-9 minutes par scan. Corrigé avec ThreadPoolExecutor (scan concurrent) — 8,5 min → 6,4 s mesuré en réel sur localhost
  • Score CVSS codé en dur : le README annonçait un calcul CVSS v3.1 mais les scores étaient des constantes choisies à la main. Implémentation de la vraie formule (Exploitability, Impact, Roundup)
  • Confusion de périmètre : les checks system analysaient la machine locale, pas la cible --target, sans que ce soit documenté nulle part. IDs renommés LOCAL-*, docstrings + README + bannière console explicites
  • Aucun garde-fou d'autorisation : l'outil scannait n'importe quelle cible sans confirmation. Ajout d'un prompt de confirmation interactif + flag --i-am-authorized pour usage non-interactif/CI

Résultats de tests réels

Scan localhost
6,4 s(SMB/445 → Critical (9.8), MSRPC/135 → Medium (5.3) — contre 8,5 min avant le fix de concurrence)
Certificat TLS expiré
Détecté correctement(expired.badssl.com — expiré depuis le 12/04/2015)
Headers de sécurité
5 headers manquants détectés(example.com — HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy)

Résultats

Stack
Python 3.11+, Jinja2, cryptography(Aucun binaire de scan externe (nmap, Nessus...))
Tests
12 tests pytest(Moteur CVSS + modèles de données)
Sortie
Rapport HTML autonome + export JSON(Compatible SIEM/ticketing)
Cadre légal
Garde-fou d'autorisation intégré(Confirmation requise ou --i-am-authorized)