Un scanner de vulnérabilités qui remonte 700 alertes ne protège personne si l’équipe ne sait pas quoi traiter aujourd’hui. Pour prioriser sérieusement, il faut croiser trois informations : les vulnérabilités réellement exploitées, les actifs présents chez vous et les échéances de correction. Le catalogue CISA KEV donne la première brique. Dans ce tutoriel, on construit un mini-tableau de bord local en Bash et Python pour suivre les CVE exploitées sans attendre un outil GRC coûteux.

L’objectif est volontairement simple : télécharger le flux JSON officiel, extraire les entrées récentes ou urgentes, produire un fichier HTML lisible et afficher une alerte quand une échéance approche. Ce n’est pas un SIEM. C’est un outil de pilotage quotidien pour une agence web, une PME ou une équipe DevOps qui veut arrêter de patcher au hasard.

Pré-requis

Il suffit d’une machine Linux, macOS ou WSL avec curl et Python 3. Le script n’a pas besoin d’accès administrateur et ne stocke aucun secret. Créez un dossier de travail :

mkdir -p ~/security/kev-dashboard
cd ~/security/kev-dashboard

On va utiliser le flux JSON public de la CISA. Il contient les champs essentiels : identifiant CVE, éditeur, produit, date d’ajout, description courte, action requise, date limite, usage connu par ransomware et liens de référence.

Étape 1 : récupérer le catalogue KEV

Commençons par un script Bash minimal. Il télécharge le flux et garde une copie datée pour pouvoir comparer les changements plus tard.

#!/usr/bin/env bash
set -euo pipefail

WORKDIR="${HOME}/security/kev-dashboard"
FEED="https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
TODAY="$(date +%F)"

mkdir -p "$WORKDIR/archive"
curl -fsSL "$FEED" -o "$WORKDIR/kev.json"
cp "$WORKDIR/kev.json" "$WORKDIR/archive/kev-$TODAY.json"

echo "Catalogue KEV mis à jour : $WORKDIR/kev.json"

Enregistrez-le dans fetch-kev.sh, puis lancez :

chmod +x fetch-kev.sh
./fetch-kev.sh

Étape 2 : générer un rapport HTML

Le script Python suivant lit le JSON, filtre les vulnérabilités ajoutées depuis 14 jours ou dont l’échéance est proche, puis génère une page HTML. Le code reste court pour être auditable.

#!/usr/bin/env python3
import json
from datetime import date, datetime, timedelta
from html import escape
from pathlib import Path

base = Path.home() / "security" / "kev-dashboard"
data = json.loads((base / "kev.json").read_text())
today = date.today()
recent_limit = today - timedelta(days=14)
due_limit = today + timedelta(days=7)

def parse_day(value):
    return datetime.strptime(value, "%Y-%m-%d").date()

rows = []
for item in data["vulnerabilities"]:
    added = parse_day(item["dateAdded"])
    due = parse_day(item["dueDate"])
    if added >= recent_limit or due <= due_limit:
        rows.append({
            "cve": item["cveID"],
            "vendor": item["vendorProject"],
            "product": item["product"],
            "name": item["vulnerabilityName"],
            "added": added,
            "due": due,
            "ransomware": item.get("knownRansomwareCampaignUse", "Unknown"),
            "notes": item.get("notes", ""),
        })

rows.sort(key=lambda row: (row["due"], row["vendor"]))

def badge(due):
    if due < today:
        return "late"
    if due <= due_limit:
        return "urgent"
    return "watch"

html_rows = []
for row in rows:
    html_rows.append(f"""<tr class="{badge(row['due'])}">
<td>{escape(row['cve'])}</td>
<td>{escape(row['vendor'])}</td>
<td>{escape(row['product'])}</td>
<td>{escape(row['name'])}</td>
<td>{row['added']}</td>
<td>{row['due']}</td>
<td>{escape(row['ransomware'])}</td>
</tr>""")

report = f"""<!doctype html>
<html lang="fr">
<meta charset="utf-8">
<title>CISA KEV dashboard</title>
<style>
body {{ font-family: system-ui, sans-serif; margin: 32px; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border-bottom: 1px solid #ddd; padding: 8px; text-align: left; }}
.late {{ background: #ffe2e2; }}
.urgent {{ background: #fff4cc; }}
.watch {{ background: #eef6ff; }}
</style>
<h1>CISA KEV dashboard</h1>
<p>Catalogue : {escape(data['catalogVersion'])} · Généré le {today}</p>
<table>
<tr><th>CVE</th><th>Éditeur</th><th>Produit</th><th>Faille</th><th>Ajout</th><th>Échéance</th><th>Ransomware</th></tr>
{''.join(html_rows)}
</table>
</html>"""

(base / "kev-dashboard.html").write_text(report)
print(f"{len(rows)} entrées à surveiller")
print(base / "kev-dashboard.html")

Enregistrez ce fichier dans build-dashboard.py, puis exécutez :

chmod +x build-dashboard.py
./fetch-kev.sh
./build-dashboard.py
open ~/security/kev-dashboard/kev-dashboard.html 2>/dev/null || true

Étape 3 : ajouter votre inventaire

Le KEV seul ne suffit pas. Il faut savoir si les produits existent chez vous. Créez un fichier assets.txt avec les mots-clés de votre parc :

WordPress
LiteLLM
Chromium
Microsoft Edge
Check Point
SolarWinds Serv-U
Cisco Catalyst SD-WAN
Arista EOS
PAN-OS
Drupal

Ajoutez ensuite ce bloc dans le script Python, juste avant la génération HTML, pour marquer les correspondances :

assets_file = base / "assets.txt"
keywords = []
if assets_file.exists():
    keywords = [line.strip().lower() for line in assets_file.read_text().splitlines() if line.strip()]

for row in rows:
    haystack = f"{row['vendor']} {row['product']} {row['name']}".lower()
    row["owned"] = any(keyword in haystack for keyword in keywords)

Dans un outil de production, l’inventaire viendrait de votre EDR, CMDB, MDM, Ansible, osquery ou même d’un export GLPI. Ici, un fichier texte suffit pour instaurer le réflexe : une CVE exploitée n’est urgente pour vous que si elle touche votre surface réelle.

Étape 4 : lancer le contrôle chaque matin

Ajoutez une tâche cron locale :

crontab -e

# Tous les jours à 8h10
10 8 * * * $HOME/security/kev-dashboard/fetch-kev.sh && $HOME/security/kev-dashboard/build-dashboard.py

Si vous voulez recevoir une alerte mail quand une échéance tombe dans moins de sept jours, ajoutez cette sortie texte au script Python :

urgent = [row for row in rows if row["due"] <= due_limit]
if urgent:
    print("ALERTE KEV : échéances proches")
    for row in urgent[:20]:
        print(f"- {row['cve']} {row['vendor']} {row['product']} avant {row['due']}")

Et redirigez-la vers votre outil habituel : email local, Slack webhook interne, Mattermost, ntfy, ou ticket automatique. Attention : ne publiez pas ce tableau sur Internet. Il révèle vos priorités de patching et donc une partie de votre exposition.

Comment l’utiliser en réunion patching

Le tableau doit répondre à trois questions en moins de cinq minutes : quelles failles sont exploitées, lesquelles concernent notre parc, lesquelles ont une date limite proche. À partir de là, classez les actions en trois files : correction immédiate, mitigation temporaire, acceptation documentée du risque.

File 1 : exposé + exploité + actif critique → patch aujourd'hui
File 2 : exploité + actif interne → patch planifié sous 72 h
File 3 : produit absent ou retiré → noter et fermer

Ce petit dashboard ne remplace pas Tenable, Qualys, Wiz ou Defender Vulnerability Management. Il vous donne en revanche une boussole indépendante, gratuite et vérifiable. C’est souvent ce qui manque dans les petites structures : moins d’alertes, plus de décisions.

Dernier conseil : conservez les archives quotidiennes. Quand un incident survient, pouvoir prouver qu’une CVE était absente hier, présente aujourd’hui, puis corrigée demain facilite le post-mortem et la discussion avec les clients. La sécurité opérationnelle est faite de traces simples, lisibles, et répétées.


Sources et références

W
WP Admin Lab

Architecte web full-stack. WordPress, performance, data et sécurité. Notes de terrain, tests reproductibles et retours d'expérience.