mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
232 lines
7.8 KiB
Python
232 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Endpoint Vulnerability Remediation Prioritizer
|
|
|
|
Parses vulnerability scan exports (Nessus CSV), enriches with EPSS scores,
|
|
cross-references CISA KEV catalog, and generates prioritized remediation plans.
|
|
"""
|
|
|
|
import json
|
|
import csv
|
|
import sys
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
from collections import defaultdict
|
|
from urllib.request import urlopen
|
|
from urllib.error import URLError
|
|
|
|
|
|
CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
|
|
EPSS_API_URL = "https://api.first.org/data/v1/epss"
|
|
|
|
|
|
def load_nessus_csv(csv_path: str) -> list:
|
|
"""Parse Nessus scan export CSV into vulnerability records."""
|
|
vulns = []
|
|
|
|
with open(csv_path, "r", encoding="utf-8-sig") as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
cvss = 0.0
|
|
try:
|
|
cvss = float(row.get("CVSS v3.0 Base Score", "0") or
|
|
row.get("CVSS v2.0 Base Score", "0") or "0")
|
|
except ValueError:
|
|
pass
|
|
|
|
cve_list = [c.strip() for c in row.get("CVE", "").split(",") if c.strip()]
|
|
|
|
vuln = {
|
|
"plugin_id": row.get("Plugin ID", ""),
|
|
"plugin_name": row.get("Name", ""),
|
|
"severity": row.get("Risk", "None"),
|
|
"host": row.get("Host", ""),
|
|
"protocol": row.get("Protocol", ""),
|
|
"port": row.get("Port", ""),
|
|
"cvss_score": cvss,
|
|
"cves": cve_list,
|
|
"solution": row.get("Solution", ""),
|
|
"synopsis": row.get("Synopsis", ""),
|
|
"description": row.get("Description", "")[:200],
|
|
}
|
|
vulns.append(vuln)
|
|
|
|
return vulns
|
|
|
|
|
|
def fetch_cisa_kev() -> set:
|
|
"""Fetch CISA Known Exploited Vulnerabilities catalog."""
|
|
try:
|
|
with urlopen(CISA_KEV_URL, timeout=15) as resp:
|
|
data = json.loads(resp.read())
|
|
return {v["cveID"] for v in data.get("vulnerabilities", [])}
|
|
except (URLError, json.JSONDecodeError, KeyError) as e:
|
|
print(f"Warning: Could not fetch CISA KEV catalog: {e}")
|
|
return set()
|
|
|
|
|
|
def fetch_epss_scores(cves: list) -> dict:
|
|
"""Fetch EPSS scores for a list of CVEs."""
|
|
epss_scores = {}
|
|
batch_size = 100
|
|
|
|
for i in range(0, len(cves), batch_size):
|
|
batch = cves[i:i + batch_size]
|
|
cve_param = ",".join(batch)
|
|
url = f"{EPSS_API_URL}?cve={cve_param}"
|
|
try:
|
|
with urlopen(url, timeout=15) as resp:
|
|
data = json.loads(resp.read())
|
|
for item in data.get("data", []):
|
|
epss_scores[item["cve"]] = float(item.get("epss", 0))
|
|
except (URLError, json.JSONDecodeError) as e:
|
|
print(f"Warning: EPSS lookup failed for batch: {e}")
|
|
|
|
return epss_scores
|
|
|
|
|
|
def prioritize_vulnerabilities(vulns: list, kev_cves: set, epss_scores: dict) -> list:
|
|
"""Assign remediation priority based on risk scoring."""
|
|
for vuln in vulns:
|
|
cvss = vuln["cvss_score"]
|
|
cves = vuln["cves"]
|
|
|
|
in_kev = any(cve in kev_cves for cve in cves)
|
|
max_epss = max((epss_scores.get(cve, 0) for cve in cves), default=0)
|
|
|
|
vuln["in_cisa_kev"] = in_kev
|
|
vuln["epss_score"] = round(max_epss, 4)
|
|
|
|
if cvss >= 9.0 or in_kev or (cvss >= 7.0 and max_epss > 0.7):
|
|
vuln["priority"] = "P1"
|
|
vuln["sla_days"] = 14
|
|
elif cvss >= 7.0 and max_epss > 0.5:
|
|
vuln["priority"] = "P2"
|
|
vuln["sla_days"] = 30
|
|
elif cvss >= 4.0:
|
|
vuln["priority"] = "P3"
|
|
vuln["sla_days"] = 60
|
|
else:
|
|
vuln["priority"] = "P4"
|
|
vuln["sla_days"] = 90
|
|
|
|
vuln["sla_deadline"] = (
|
|
datetime.utcnow() + timedelta(days=vuln["sla_days"])
|
|
).strftime("%Y-%m-%d")
|
|
|
|
vulns.sort(key=lambda v: (
|
|
{"P1": 0, "P2": 1, "P3": 2, "P4": 3}.get(v["priority"], 4),
|
|
-v["cvss_score"],
|
|
-v["epss_score"],
|
|
))
|
|
|
|
return vulns
|
|
|
|
|
|
def generate_remediation_plan(vulns: list, output_path: str) -> None:
|
|
"""Generate prioritized remediation plan in JSON."""
|
|
summary = defaultdict(int)
|
|
hosts_affected = defaultdict(set)
|
|
|
|
for vuln in vulns:
|
|
summary[vuln["priority"]] += 1
|
|
hosts_affected[vuln["priority"]].add(vuln["host"])
|
|
|
|
plan = {
|
|
"report_generated": datetime.utcnow().isoformat() + "Z",
|
|
"summary": {
|
|
"total_vulnerabilities": len(vulns),
|
|
"by_priority": dict(summary),
|
|
"unique_hosts": {p: len(h) for p, h in hosts_affected.items()},
|
|
},
|
|
"kev_vulnerabilities": [v for v in vulns if v.get("in_cisa_kev")],
|
|
"remediation_queue": [
|
|
{
|
|
"priority": v["priority"],
|
|
"sla_deadline": v["sla_deadline"],
|
|
"host": v["host"],
|
|
"plugin_name": v["plugin_name"],
|
|
"cvss": v["cvss_score"],
|
|
"epss": v["epss_score"],
|
|
"cisa_kev": v["in_cisa_kev"],
|
|
"cves": v["cves"],
|
|
"solution": v["solution"],
|
|
}
|
|
for v in vulns
|
|
],
|
|
}
|
|
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
json.dump(plan, f, indent=2)
|
|
|
|
|
|
def export_remediation_csv(vulns: list, output_path: str) -> None:
|
|
"""Export remediation plan to CSV for team assignment."""
|
|
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
|
writer = csv.writer(f)
|
|
writer.writerow([
|
|
"Priority", "SLA Deadline", "Host", "Vulnerability",
|
|
"CVSS", "EPSS", "CISA KEV", "CVEs", "Solution",
|
|
"Status", "Assigned To", "Notes",
|
|
])
|
|
for v in vulns:
|
|
writer.writerow([
|
|
v["priority"], v["sla_deadline"], v["host"],
|
|
v["plugin_name"], v["cvss_score"], v["epss_score"],
|
|
v["in_cisa_kev"], "; ".join(v["cves"]),
|
|
v["solution"][:200], "Open", "", "",
|
|
])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python process.py <nessus_export.csv>")
|
|
print()
|
|
print("Parses Nessus CSV export, enriches with EPSS/CISA KEV data,")
|
|
print("and generates a prioritized remediation plan.")
|
|
sys.exit(1)
|
|
|
|
csv_path = sys.argv[1]
|
|
if not os.path.exists(csv_path):
|
|
print(f"Error: File not found: {csv_path}")
|
|
sys.exit(1)
|
|
|
|
print("Parsing vulnerability scan results...")
|
|
vulns = load_nessus_csv(csv_path)
|
|
print(f"Loaded {len(vulns)} vulnerability findings")
|
|
|
|
all_cves = list({cve for v in vulns for cve in v["cves"]})
|
|
print(f"Unique CVEs: {len(all_cves)}")
|
|
|
|
print("Fetching CISA KEV catalog...")
|
|
kev_cves = fetch_cisa_kev()
|
|
print(f"KEV catalog contains {len(kev_cves)} CVEs")
|
|
|
|
print("Fetching EPSS scores...")
|
|
epss_scores = fetch_epss_scores(all_cves) if all_cves else {}
|
|
print(f"Retrieved EPSS scores for {len(epss_scores)} CVEs")
|
|
|
|
print("Prioritizing vulnerabilities...")
|
|
vulns = prioritize_vulnerabilities(vulns, kev_cves, epss_scores)
|
|
|
|
base = os.path.splitext(os.path.basename(csv_path))[0]
|
|
out_dir = os.path.dirname(csv_path) or "."
|
|
|
|
plan_path = os.path.join(out_dir, f"{base}_remediation_plan.json")
|
|
generate_remediation_plan(vulns, plan_path)
|
|
print(f"Remediation plan: {plan_path}")
|
|
|
|
csv_out = os.path.join(out_dir, f"{base}_remediation.csv")
|
|
export_remediation_csv(vulns, csv_out)
|
|
print(f"Remediation CSV: {csv_out}")
|
|
|
|
print(f"\n--- Remediation Summary ---")
|
|
for p in ["P1", "P2", "P3", "P4"]:
|
|
count = sum(1 for v in vulns if v["priority"] == p)
|
|
if count:
|
|
print(f" {p}: {count} vulnerabilities")
|
|
|
|
kev_count = sum(1 for v in vulns if v.get("in_cisa_kev"))
|
|
if kev_count:
|
|
print(f"\n CISA KEV matches: {kev_count} (mandatory remediation)")
|