npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
MITRE ATLAS
Overview
The CISA Known Exploited Vulnerabilities (KEV) catalog, established through Binding Operational Directive (BOD) 22-01, is a living list of CVEs that have been actively exploited in the wild and carry significant risk. As of early 2026, the catalog contains over 1,484 entries, growing 20% in 2025 alone with 245 new additions. This skill covers integrating the KEV catalog into vulnerability prioritization workflows alongside EPSS (Exploit Prediction Scoring System) and CVSS to create a risk-based approach that prioritizes vulnerabilities with confirmed exploitation activity over theoretical severity alone.
When to Use
- When conducting security assessments that involve performing cve prioritization with kev catalog
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- Access to vulnerability scan results (Qualys, Nessus, Rapid7, etc.)
- Familiarity with CVE identifiers and NVD
- Understanding of CVSS scoring (v3.1 and v4.0)
- API access to CISA KEV, EPSS, and NVD endpoints
- Python 3.8+ with requests and pandas libraries
Core Concepts
CISA KEV Catalog Structure
Each KEV entry contains:
- CVE ID: The CVE identifier (e.g., CVE-2024-3094)
- Vendor/Project: Affected vendor and product name
- Vulnerability Name: Short description of the vulnerability
- Date Added: When CISA added it to the catalog
- Short Description: Brief technical description
- Required Action: Recommended remediation action
- Due Date: Deadline for federal agencies (FCEB) to remediate
- Known Ransomware Campaign Use: Whether ransomware groups exploit it
BOD 22-01 Remediation Timelines
| CVE Publication Date | Remediation Deadline |
|---|---|
| 2021 or later | 2 weeks from KEV listing |
| Before 2021 | 6 months from KEV listing |
Multi-Factor Prioritization Model
| Factor | Weight | Data Source | Rationale |
|---|---|---|---|
| CISA KEV Listed | 30% | CISA KEV JSON feed | Confirmed active exploitation |
| EPSS Score | 25% | FIRST EPSS API | Predicted exploitation probability |
| CVSS Base Score | 20% | NVD API v2.0 | Intrinsic vulnerability severity |
| Asset Criticality | 15% | CMDB/Asset inventory | Business impact context |
| Network Exposure | 10% | Network architecture | Attack surface accessibility |
KEV + EPSS Decision Matrix
| KEV Listed | EPSS > 0.5 | CVSS >= 9.0 | Priority | SLA |
|---|---|---|---|---|
| Yes | Any | Any | P1-Emergency | 48 hours |
| No | Yes | Yes | P1-Emergency | 48 hours |
| No | Yes | No | P2-Critical | 7 days |
| No | No | Yes | P2-Critical | 7 days |
| No | No | No (>= 7.0) | P3-High | 14 days |
| No | No | No (>= 4.0) | P4-Medium | 30 days |
| No | No | No (< 4.0) | P5-Low | 90 days |
Workflow
Step 1: Fetch and Parse the KEV Catalog
import requests
import json
from datetime import datetime
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
def fetch_kev_catalog():
"""Download and parse the CISA KEV catalog."""
response = requests.get(KEV_URL, timeout=30)
response.raise_for_status()
data = response.json()
catalog = {}
for vuln in data.get("vulnerabilities", []):
cve_id = vuln["cveID"]
catalog[cve_id] = {
"vendor": vuln.get("vendorProject", ""),
"product": vuln.get("product", ""),
"name": vuln.get("vulnerabilityName", ""),
"date_added": vuln.get("dateAdded", ""),
"description": vuln.get("shortDescription", ""),
"action": vuln.get("requiredAction", ""),
"due_date": vuln.get("dueDate", ""),
"ransomware_use": vuln.get("knownRansomwareCampaignUse", "Unknown"),
}
print(f"[+] Loaded {len(catalog)} CVEs from CISA KEV catalog")
print(f" Catalog version: {data.get('catalogVersion', 'N/A')}")
print(f" Last updated: {data.get('dateReleased', 'N/A')}")
return catalog
kev = fetch_kev_catalog()Step 2: Enrich with EPSS Scores
EPSS_API = "https://api.first.org/data/v1/epss"
def get_epss_scores(cve_list):
"""Fetch EPSS scores for a batch of CVEs."""
scores = {}
batch_size = 100
for i in range(0, len(cve_list), batch_size):
batch = cve_list[i:i + batch_size]
cve_param = ",".join(batch)
response = requests.get(EPSS_API, params={"cve": cve_param}, timeout=30)
if response.status_code == 200:
for entry in response.json().get("data", []):
scores[entry["cve"]] = {
"epss": float(entry.get("epss", 0)),
"percentile": float(entry.get("percentile", 0)),
}
return scoresStep 3: Build the Prioritization Engine
import pandas as pd
def prioritize_vulnerabilities(scan_results, kev_catalog, epss_scores):
"""Apply multi-factor prioritization to scan results."""
prioritized = []
for vuln in scan_results:
cve_id = vuln.get("cve_id", "")
cvss_score = float(vuln.get("cvss_score", 0))
asset_criticality = float(vuln.get("asset_criticality", 3))
exposure = float(vuln.get("network_exposure", 3))
in_kev = cve_id in kev_catalog
kev_data = kev_catalog.get(cve_id, {})
epss_data = epss_scores.get(cve_id, {"epss": 0, "percentile": 0})
epss_score = epss_data["epss"]
# Composite risk score calculation
risk_score = (
(1.0 if in_kev else 0.0) * 10 * 0.30 +
epss_score * 10 * 0.25 +
cvss_score * 0.20 +
(asset_criticality / 5.0) * 10 * 0.15 +
(exposure / 5.0) * 10 * 0.10
)
# Assign priority level
if in_kev or (epss_score > 0.5 and cvss_score >= 9.0):
priority = "P1-Emergency"
sla_days = 2
elif epss_score > 0.5 or cvss_score >= 9.0:
priority = "P2-Critical"
sla_days = 7
elif cvss_score >= 7.0:
priority = "P3-High"
sla_days = 14
elif cvss_score >= 4.0:
priority = "P4-Medium"
sla_days = 30
else:
priority = "P5-Low"
sla_days = 90
prioritized.append({
"cve_id": cve_id,
"cvss_score": cvss_score,
"epss_score": round(epss_score, 4),
"epss_percentile": round(epss_data["percentile"], 4),
"in_cisa_kev": in_kev,
"ransomware_use": kev_data.get("ransomware_use", "N/A"),
"kev_due_date": kev_data.get("due_date", "N/A"),
"risk_score": round(risk_score, 2),
"priority": priority,
"sla_days": sla_days,
"asset": vuln.get("asset", ""),
"asset_criticality": asset_criticality,
})
df = pd.DataFrame(prioritized)
df = df.sort_values("risk_score", ascending=False)
return dfStep 4: Generate Prioritization Report
def generate_report(df, output_file="kev_prioritized_report.csv"):
"""Generate summary report from prioritized vulnerabilities."""
print("\n" + "=" * 70)
print("VULNERABILITY PRIORITIZATION REPORT - KEV + EPSS + CVSS")
print("=" * 70)
print(f"\nTotal vulnerabilities analyzed: {len(df)}")
print(f"KEV-listed vulnerabilities: {df['in_cisa_kev'].sum()}")
print(f"Ransomware-associated: {(df['ransomware_use'] == 'Known').sum()}")
print("\nPriority Distribution:")
print(df["priority"].value_counts().to_string())
print("\nTop 15 Highest Risk Vulnerabilities:")
top = df.head(15)[["cve_id", "cvss_score", "epss_score", "in_cisa_kev",
"risk_score", "priority"]]
print(top.to_string(index=False))
df.to_csv(output_file, index=False)
print(f"\n[+] Full report saved to: {output_file}")Best Practices
- Update the KEV catalog daily since CISA adds new entries multiple times per week
- Always cross-reference KEV with EPSS; a CVE may have high EPSS but not yet be in KEV
- Treat all KEV-listed CVEs as P1-Emergency regardless of CVSS score
- Pay special attention to KEV entries flagged with "Known Ransomware Campaign Use"
- Automate KEV comparison against your vulnerability scan results in CI/CD pipelines
- Track KEV due dates separately for FCEB compliance requirements
- Use KEV as a leading indicator for threat hunting; if a CVE is added, check for prior exploitation in your environment
Common Pitfalls
- Relying solely on CVSS scores without checking KEV or EPSS data
- Not updating the KEV catalog frequently enough (CISA updates multiple times weekly)
- Treating non-KEV CVEs as safe; they may be exploited but not yet cataloged
- Ignoring the "ransomware use" field which indicates highest-urgency threats
- Using KEV only for compliance instead of integrating into overall risk management
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md5.2 KB
API Reference: CISA KEV Catalog CVE Prioritization
Libraries Used
| Library | Purpose |
|---|---|
requests |
Fetch KEV catalog JSON from CISA |
json |
Parse vulnerability entries and match against scan data |
csv |
Read vulnerability scanner CSV exports |
datetime |
Calculate remediation deadlines and SLA compliance |
Installation
pip install requestsData Sources
CISA KEV JSON Feed
URL: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
Format: JSON
Authentication: None (public)
Update frequency: Updated as new exploited CVEs are added (typically several times per week)CISA KEV CSV Feed
URL: https://www.cisa.gov/sites/default/files/csv/known_exploited_vulnerabilities.csv
Format: CSVGitHub Mirror
URL: https://raw.githubusercontent.com/cisagov/kev-data/main/known_exploited_vulnerabilities.jsonCore Operations
Fetch the KEV Catalog
import requests
from datetime import datetime
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
def fetch_kev_catalog():
resp = requests.get(KEV_URL, timeout=30)
resp.raise_for_status()
data = resp.json()
return {
"title": data["title"],
"catalog_version": data["catalogVersion"],
"date_released": data["dateReleased"],
"count": data["count"],
"vulnerabilities": data["vulnerabilities"],
}KEV Entry Schema
| Field | Type | Description |
|---|---|---|
cveID |
string | CVE identifier (e.g., "CVE-2024-12345") |
vendorProject |
string | Affected vendor (e.g., "Microsoft") |
product |
string | Affected product (e.g., "Windows") |
vulnerabilityName |
string | Human-readable vulnerability description |
dateAdded |
string | Date added to KEV (YYYY-MM-DD) |
shortDescription |
string | Brief vulnerability description |
requiredAction |
string | CISA-recommended remediation action |
dueDate |
string | Remediation deadline for federal agencies (YYYY-MM-DD) |
knownRansomwareCampaignUse |
string | "Known" or "Unknown" ransomware association |
notes |
string | Additional context |
Match Scan Results Against KEV
def match_scan_to_kev(scan_cves, kev_catalog):
"""Cross-reference vulnerability scan CVEs against the KEV catalog."""
kev_lookup = {v["cveID"]: v for v in kev_catalog["vulnerabilities"]}
matched = []
unmatched = []
for cve_id in scan_cves:
if cve_id in kev_lookup:
entry = kev_lookup[cve_id]
matched.append({
"cve": cve_id,
"vendor": entry["vendorProject"],
"product": entry["product"],
"due_date": entry["dueDate"],
"ransomware": entry["knownRansomwareCampaignUse"],
"action": entry["requiredAction"],
"overdue": datetime.strptime(entry["dueDate"], "%Y-%m-%d") < datetime.now(),
})
else:
unmatched.append(cve_id)
return {"kev_matches": matched, "non_kev": unmatched}Prioritize by Risk
def prioritize_kev_findings(kev_matches):
"""Sort KEV matches by priority: overdue > ransomware > due date."""
def priority_key(entry):
score = 0
if entry["overdue"]:
score += 1000
if entry["ransomware"] == "Known":
score += 500
# Earlier due dates get higher priority
days_until = (datetime.strptime(entry["due_date"], "%Y-%m-%d") - datetime.now()).days
score -= days_until
return -score
return sorted(kev_matches, key=priority_key)Generate Remediation Report
def generate_report(scan_results, kev_catalog):
matches = match_scan_to_kev(scan_results, kev_catalog)
overdue = [m for m in matches["kev_matches"] if m["overdue"]]
ransomware = [m for m in matches["kev_matches"] if m["ransomware"] == "Known"]
return {
"total_vulns_scanned": len(scan_results),
"kev_matches": len(matches["kev_matches"]),
"overdue_count": len(overdue),
"ransomware_associated": len(ransomware),
"critical_actions": prioritize_kev_findings(matches["kev_matches"])[:10],
"non_kev_vulns": len(matches["non_kev"]),
}Monitor KEV Catalog Updates
def check_for_new_entries(last_known_count):
"""Check if new vulnerabilities have been added to KEV."""
catalog = fetch_kev_catalog()
current_count = catalog["count"]
if current_count > last_known_count:
new_entries = catalog["vulnerabilities"][last_known_count:]
return {
"new_entries": len(new_entries),
"latest": new_entries,
"total": current_count,
}
return {"new_entries": 0, "total": current_count}Output Format
{
"catalog_version": "2025.01.15",
"total_kev_entries": 1150,
"scan_matches": 12,
"overdue": 3,
"ransomware_associated": 5,
"critical_actions": [
{
"cve": "CVE-2024-21887",
"vendor": "Ivanti",
"product": "Connect Secure",
"due_date": "2024-01-31",
"ransomware": "Known",
"overdue": true,
"action": "Apply mitigations per vendor instructions or discontinue use."
}
]
}standards.md1.2 KB
Standards and References - CVE Prioritization with KEV Catalog
Official CISA Resources
- CISA KEV Catalog: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
- KEV JSON Feed: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
- BOD 22-01: https://www.cisa.gov/binding-operational-directive-22-01
- CISA SSVC Decision Tree: https://www.cisa.gov/ssvc
Scoring Systems
- FIRST EPSS API: https://api.first.org/data/v1/epss
- EPSS Model Documentation: https://www.first.org/epss/model
- CVSS v4.0 Specification: https://www.first.org/cvss/specification-document
- NVD API v2.0: https://nvd.nist.gov/developers/vulnerabilities
KEV Catalog Statistics (2025)
| Metric | Value |
|---|---|
| Total CVEs in catalog | 1,484+ |
| Added in 2025 | 245 |
| Year-over-year growth | 20% |
| Ransomware-associated (2025) | 24 |
Regulatory Requirements for KEV Remediation
- BOD 22-01: Federal agencies must remediate KEV CVEs per due dates
- PCI DSS v4.0: Prioritize remediation of actively exploited vulns
- NIST CSF 2.0: Risk-based vulnerability prioritization
- ISO 27001:2022 A.8.8: Technical vulnerability management
workflows.md2.8 KB
Workflows - CVE Prioritization with KEV Catalog
Workflow 1: Daily KEV Integration Pipeline
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Fetch KEV JSON │────>│ Compare with │────>│ Identify New │
│ Feed (daily) │ │ Previous Version │ │ KEV Entries │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
┌────────────────────────────────────────────────┘
v
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Cross-Reference │────>│ Flag Matching │────>│ Escalate to P1 │
│ Scan Results │ │ Vulns in Env │ │ Emergency │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
v
┌──────────────────┐ ┌──────────────────┐
│ Notify Remediation│───>│ Track Against │
│ Teams │ │ KEV Due Date │
└──────────────────┘ └──────────────────┘Workflow 2: Multi-Factor Scoring Pipeline
For each CVE in scan results:
1. Look up CVSS base score from NVD API
2. Fetch EPSS probability from FIRST API
3. Check presence in CISA KEV catalog
4. Check if ransomware-associated in KEV
5. Look up asset criticality from CMDB
6. Determine network exposure (internet/DMZ/internal)
7. Calculate composite risk score
8. Assign priority level (P1-P5)
9. Set remediation SLA based on priority
10. Generate ticket in ITSM systemWorkflow 3: KEV-Triggered Threat Hunt
When new CVE added to KEV:
├── Check if vulnerability exists in environment
│ ├── Yes: Immediate P1 escalation
│ │ ├── Search SIEM for exploitation indicators
│ │ ├── Check EDR for related TTPs
│ │ └── Initiate incident response if exploitation found
│ └── No: Document non-applicability
└── Update threat intelligence feeds with KEV IOCsScripts 2
agent.py8.7 KB
#!/usr/bin/env python3
"""CISA KEV CVE prioritization agent.
Downloads the CISA Known Exploited Vulnerabilities (KEV) catalog and
cross-references it against a list of CVEs from vulnerability scans to
prioritize remediation based on active exploitation status, due dates,
and vendor/product impact.
"""
import argparse
import csv
import io
import json
import os
import sys
from datetime import datetime, timezone
try:
import requests
except ImportError:
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
sys.exit(1)
KEV_JSON_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
def download_kev_catalog():
"""Download the latest CISA KEV catalog as JSON."""
print(f"[*] Downloading CISA KEV catalog from {KEV_JSON_URL}")
resp = requests.get(KEV_JSON_URL, timeout=30)
resp.raise_for_status()
data = resp.json()
vulns = data.get("vulnerabilities", [])
catalog_version = data.get("catalogVersion", "unknown")
count = data.get("count", len(vulns))
date_released = data.get("dateReleased", "unknown")
print(f"[+] KEV catalog v{catalog_version}: {count} vulnerabilities "
f"(released: {date_released})")
return vulns, {
"catalog_version": catalog_version,
"count": count,
"date_released": date_released,
}
def build_kev_index(kev_vulns):
"""Build a lookup dict keyed by CVE ID for fast matching."""
index = {}
for vuln in kev_vulns:
cve_id = vuln.get("cveID", "")
if cve_id:
index[cve_id.upper()] = {
"cve_id": cve_id,
"vendor": vuln.get("vendorProject", ""),
"product": vuln.get("product", ""),
"vulnerability_name": vuln.get("vulnerabilityName", ""),
"date_added": vuln.get("dateAdded", ""),
"short_description": vuln.get("shortDescription", ""),
"required_action": vuln.get("requiredAction", ""),
"due_date": vuln.get("dueDate", ""),
"known_ransomware_campaign": vuln.get("knownRansomwareCampaignUse", "Unknown"),
"notes": vuln.get("notes", ""),
}
return index
def load_cve_list(source):
"""Load CVE list from file (one CVE per line or CSV) or comma-separated string."""
cves = []
if os.path.isfile(source):
with open(source, "r") as f:
content = f.read()
for line in content.strip().splitlines():
line = line.strip().strip(",").strip('"')
if line.upper().startswith("CVE-"):
cves.append(line.upper())
elif "," in line:
parts = line.split(",")
for part in parts:
part = part.strip().strip('"')
if part.upper().startswith("CVE-"):
cves.append(part.upper())
else:
for part in source.split(","):
part = part.strip()
if part.upper().startswith("CVE-"):
cves.append(part.upper())
return list(set(cves))
def prioritize_cves(cve_list, kev_index):
"""Cross-reference CVEs against KEV catalog and prioritize."""
in_kev = []
not_in_kev = []
for cve_id in cve_list:
kev_entry = kev_index.get(cve_id)
if kev_entry:
entry = dict(kev_entry)
entry["in_kev"] = True
entry["priority"] = "CRITICAL"
if entry.get("known_ransomware_campaign", "").lower() == "known":
entry["priority"] = "CRITICAL-RANSOMWARE"
try:
due = datetime.strptime(entry["due_date"], "%Y-%m-%d")
if due < datetime.now():
entry["overdue"] = True
entry["priority"] = "CRITICAL-OVERDUE"
else:
entry["overdue"] = False
except (ValueError, KeyError):
entry["overdue"] = False
in_kev.append(entry)
else:
not_in_kev.append({
"cve_id": cve_id,
"in_kev": False,
"priority": "STANDARD",
})
# Sort: overdue first, then ransomware, then by due date
priority_order = {"CRITICAL-OVERDUE": 0, "CRITICAL-RANSOMWARE": 1, "CRITICAL": 2}
in_kev.sort(key=lambda x: (priority_order.get(x["priority"], 9), x.get("due_date", "")))
return in_kev, not_in_kev
def format_summary(in_kev, not_in_kev, total_cves, catalog_info):
"""Print a human-readable prioritization report."""
print(f"\n{'='*60}")
print(f" CISA KEV CVE Prioritization Report")
print(f"{'='*60}")
print(f" KEV Catalog : v{catalog_info['catalog_version']} "
f"({catalog_info['count']} total KEVs)")
print(f" Input CVEs : {total_cves}")
print(f" In KEV : {len(in_kev)} (actively exploited)")
print(f" Not in KEV : {len(not_in_kev)}")
overdue = [v for v in in_kev if v.get("overdue")]
ransomware = [v for v in in_kev if "RANSOMWARE" in v.get("priority", "")]
print(f"\n Priority Breakdown:")
print(f" CRITICAL-OVERDUE : {len(overdue)}")
print(f" CRITICAL-RANSOMWARE : {len(ransomware)}")
print(f" CRITICAL (in KEV) : {len(in_kev)}")
print(f" STANDARD (not in KEV): {len(not_in_kev)}")
if in_kev:
print(f"\n KEV-Listed CVEs (prioritize remediation):")
for v in in_kev[:20]:
overdue_flag = " [OVERDUE]" if v.get("overdue") else ""
ransomware_flag = " [RANSOMWARE]" if "RANSOMWARE" in v.get("priority", "") else ""
print(f" {v['cve_id']:18s} | Due: {v.get('due_date', 'N/A'):10s} | "
f"{v.get('vendor', ''):15s} | {v.get('product', ''):20s}"
f"{overdue_flag}{ransomware_flag}")
if v.get("required_action"):
print(f" Action: {v['required_action'][:70]}")
def kev_stats(kev_vulns):
"""Compute statistics about the KEV catalog."""
by_vendor = {}
ransomware_count = 0
for v in kev_vulns:
vendor = v.get("vendorProject", "Unknown")
by_vendor[vendor] = by_vendor.get(vendor, 0) + 1
if v.get("knownRansomwareCampaignUse", "").lower() == "known":
ransomware_count += 1
print(f"\n KEV Catalog Statistics:")
print(f" Total vulnerabilities : {len(kev_vulns)}")
print(f" Ransomware-associated : {ransomware_count}")
print(f" Top vendors:")
for vendor, count in sorted(by_vendor.items(), key=lambda x: -x[1])[:10]:
print(f" {vendor:30s}: {count}")
def main():
parser = argparse.ArgumentParser(
description="CISA KEV CVE prioritization agent"
)
parser.add_argument("--cves", required=True,
help="CVE list: comma-separated string or path to file")
parser.add_argument("--kev-file",
help="Path to local KEV JSON (skip download)")
parser.add_argument("--stats", action="store_true",
help="Show KEV catalog statistics")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
if args.kev_file:
print(f"[*] Loading local KEV file: {args.kev_file}")
with open(args.kev_file, "r") as f:
data = json.load(f)
kev_vulns = data.get("vulnerabilities", [])
catalog_info = {
"catalog_version": data.get("catalogVersion", "local"),
"count": len(kev_vulns),
"date_released": data.get("dateReleased", "unknown"),
}
else:
kev_vulns, catalog_info = download_kev_catalog()
if args.stats:
kev_stats(kev_vulns)
kev_index = build_kev_index(kev_vulns)
cve_list = load_cve_list(args.cves)
print(f"[*] Loaded {len(cve_list)} CVE(s) to check")
in_kev, not_in_kev = prioritize_cves(cve_list, kev_index)
format_summary(in_kev, not_in_kev, len(cve_list), catalog_info)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "CISA KEV Prioritization",
"catalog_info": catalog_info,
"input_cve_count": len(cve_list),
"kev_matched": len(in_kev),
"kev_unmatched": len(not_in_kev),
"prioritized": in_kev,
"standard_priority": not_in_kev,
"risk_level": (
"CRITICAL" if any(v.get("overdue") for v in in_kev)
else "HIGH" if in_kev
else "LOW"
),
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
process.py9.4 KB
#!/usr/bin/env python3
"""
CISA KEV-Based CVE Prioritization Engine
Fetches the CISA Known Exploited Vulnerabilities catalog,
enriches with EPSS scores, and prioritizes against scan results.
Requirements:
pip install requests pandas
Usage:
python process.py fetch # Download KEV catalog
python process.py check --cve CVE-2024-3094 # Check single CVE
python process.py prioritize --csv vulns.csv --output prioritized.csv
python process.py stats # KEV catalog statistics
"""
import argparse
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path
import pandas as pd
import requests
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
EPSS_API = "https://api.first.org/data/v1/epss"
NVD_API = "https://services.nvd.nist.gov/rest/json/cves/2.0"
KEV_CACHE = "kev_catalog.json"
class KEVPrioritizer:
"""CISA KEV-based vulnerability prioritization engine."""
def __init__(self):
self.kev_catalog = {}
self.session = requests.Session()
self.session.headers.update({"User-Agent": "KEV-Prioritizer/1.0"})
def fetch_kev(self, cache_file=KEV_CACHE):
"""Download and cache the KEV catalog."""
print("[*] Fetching CISA KEV catalog...")
response = self.session.get(KEV_URL, timeout=30)
response.raise_for_status()
data = response.json()
for vuln in data.get("vulnerabilities", []):
cve_id = vuln["cveID"]
self.kev_catalog[cve_id] = {
"vendor": vuln.get("vendorProject", ""),
"product": vuln.get("product", ""),
"name": vuln.get("vulnerabilityName", ""),
"date_added": vuln.get("dateAdded", ""),
"description": vuln.get("shortDescription", ""),
"action": vuln.get("requiredAction", ""),
"due_date": vuln.get("dueDate", ""),
"ransomware_use": vuln.get("knownRansomwareCampaignUse", "Unknown"),
}
with open(cache_file, "w") as f:
json.dump(data, f)
print(f"[+] Loaded {len(self.kev_catalog)} CVEs from KEV catalog")
print(f" Version: {data.get('catalogVersion', 'N/A')}")
print(f" Released: {data.get('dateReleased', 'N/A')}")
return self.kev_catalog
def load_cached_kev(self, cache_file=KEV_CACHE):
"""Load KEV catalog from local cache."""
if not Path(cache_file).exists():
return self.fetch_kev(cache_file)
with open(cache_file) as f:
data = json.load(f)
for vuln in data.get("vulnerabilities", []):
cve_id = vuln["cveID"]
self.kev_catalog[cve_id] = {
"vendor": vuln.get("vendorProject", ""),
"product": vuln.get("product", ""),
"name": vuln.get("vulnerabilityName", ""),
"date_added": vuln.get("dateAdded", ""),
"description": vuln.get("shortDescription", ""),
"action": vuln.get("requiredAction", ""),
"due_date": vuln.get("dueDate", ""),
"ransomware_use": vuln.get("knownRansomwareCampaignUse", "Unknown"),
}
print(f"[+] Loaded {len(self.kev_catalog)} CVEs from cache")
return self.kev_catalog
def get_epss_batch(self, cve_list):
"""Fetch EPSS scores for a batch of CVEs."""
scores = {}
batch_size = 100
for i in range(0, len(cve_list), batch_size):
batch = cve_list[i:i + batch_size]
try:
response = self.session.get(
EPSS_API, params={"cve": ",".join(batch)}, timeout=30
)
if response.status_code == 200:
for entry in response.json().get("data", []):
scores[entry["cve"]] = {
"epss": float(entry.get("epss", 0)),
"percentile": float(entry.get("percentile", 0)),
}
except Exception as e:
print(f" [!] EPSS batch error: {e}")
return scores
def prioritize(self, vulnerabilities):
"""Prioritize vulnerabilities using KEV + EPSS + CVSS."""
cve_list = [v.get("cve_id", "") for v in vulnerabilities if v.get("cve_id")]
epss_scores = self.get_epss_batch(cve_list)
results = []
for vuln in vulnerabilities:
cve_id = vuln.get("cve_id", "")
cvss = float(vuln.get("cvss_score", 0))
asset_crit = float(vuln.get("asset_criticality", 3))
exposure = float(vuln.get("network_exposure", 3))
in_kev = cve_id in self.kev_catalog
kev_data = self.kev_catalog.get(cve_id, {})
epss = epss_scores.get(cve_id, {"epss": 0, "percentile": 0})
risk_score = (
(1.0 if in_kev else 0.0) * 10 * 0.30 +
epss["epss"] * 10 * 0.25 +
cvss * 0.20 +
(asset_crit / 5.0) * 10 * 0.15 +
(exposure / 5.0) * 10 * 0.10
)
if in_kev or (epss["epss"] > 0.5 and cvss >= 9.0):
priority, sla = "P1-Emergency", 2
elif epss["epss"] > 0.5 or cvss >= 9.0:
priority, sla = "P2-Critical", 7
elif cvss >= 7.0:
priority, sla = "P3-High", 14
elif cvss >= 4.0:
priority, sla = "P4-Medium", 30
else:
priority, sla = "P5-Low", 90
results.append({
**vuln,
"in_cisa_kev": in_kev,
"ransomware_use": kev_data.get("ransomware_use", "N/A"),
"kev_due_date": kev_data.get("due_date", "N/A"),
"epss_score": round(epss["epss"], 4),
"epss_percentile": round(epss["percentile"], 4),
"risk_score": round(risk_score, 2),
"priority": priority,
"sla_days": sla,
})
df = pd.DataFrame(results)
return df.sort_values("risk_score", ascending=False)
def check_cve(self, cve_id):
"""Check a single CVE against KEV and EPSS."""
in_kev = cve_id in self.kev_catalog
kev_data = self.kev_catalog.get(cve_id, {})
epss = self.get_epss_batch([cve_id]).get(cve_id, {"epss": 0, "percentile": 0})
print(f"\n{'=' * 60}")
print(f"CVE: {cve_id}")
print(f"In CISA KEV: {'YES' if in_kev else 'No'}")
if in_kev:
print(f" Vendor: {kev_data.get('vendor', 'N/A')}")
print(f" Product: {kev_data.get('product', 'N/A')}")
print(f" Date Added: {kev_data.get('date_added', 'N/A')}")
print(f" Due Date: {kev_data.get('due_date', 'N/A')}")
print(f" Ransomware Use: {kev_data.get('ransomware_use', 'N/A')}")
print(f" Action: {kev_data.get('action', 'N/A')}")
print(f"EPSS Score: {epss['epss']:.4f} ({epss['percentile'] * 100:.1f}th pct)")
def catalog_stats(self):
"""Print KEV catalog statistics."""
if not self.kev_catalog:
print("[!] KEV catalog not loaded.")
return
df = pd.DataFrame(self.kev_catalog.values())
print(f"\n{'=' * 60}")
print(f"CISA KEV CATALOG STATISTICS")
print(f"{'=' * 60}")
print(f"Total CVEs: {len(self.kev_catalog)}")
ransomware = df[df["ransomware_use"] == "Known"]
print(f"Ransomware-associated: {len(ransomware)}")
print(f"\nTop 10 Vendors:")
print(df["vendor"].value_counts().head(10).to_string())
df["date_added"] = pd.to_datetime(df["date_added"], errors="coerce")
df["year_added"] = df["date_added"].dt.year
print(f"\nAdditions by Year:")
print(df["year_added"].value_counts().sort_index().to_string())
def main():
parser = argparse.ArgumentParser(description="CISA KEV CVE Prioritization Engine")
subparsers = parser.add_subparsers(dest="command")
subparsers.add_parser("fetch", help="Download/update KEV catalog")
subparsers.add_parser("stats", help="Show KEV catalog statistics")
check_p = subparsers.add_parser("check", help="Check a single CVE")
check_p.add_argument("--cve", required=True, help="CVE ID to check")
pri_p = subparsers.add_parser("prioritize", help="Prioritize vulns from CSV")
pri_p.add_argument("--csv", required=True, help="Input CSV (needs cve_id column)")
pri_p.add_argument("--output", default="kev_prioritized.csv", help="Output CSV")
args = parser.parse_args()
engine = KEVPrioritizer()
if args.command == "fetch":
engine.fetch_kev()
elif args.command == "stats":
engine.load_cached_kev()
engine.catalog_stats()
elif args.command == "check":
engine.load_cached_kev()
engine.check_cve(args.cve)
elif args.command == "prioritize":
engine.load_cached_kev()
df = pd.read_csv(args.csv)
vulns = df.to_dict("records")
result = engine.prioritize(vulns)
result.to_csv(args.output, index=False)
print(f"\n[+] Prioritized {len(result)} vulnerabilities to {args.output}")
print(f"\nPriority Distribution:")
print(result["priority"].value_counts().to_string())
else:
parser.print_help()
if __name__ == "__main__":
main()