npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
The Stakeholder-Specific Vulnerability Categorization (SSVC) framework, developed by Carnegie Mellon University's Software Engineering Institute (SEI) in collaboration with CISA, provides a structured decision-tree methodology for vulnerability prioritization. Unlike CVSS alone, SSVC accounts for exploitation status, technical impact, automatability, mission prevalence, and public well-being impact to produce one of four actionable outcomes: Track, Track*, Attend, or Act.
When to Use
- When managing security operations that require triaging vulnerabilities with ssvc framework
- When improving security program maturity and operational processes
- When establishing standardized procedures for security team workflows
- When integrating threat intelligence or vulnerability data into operations
Prerequisites
- Python 3.9+ with
requests,pandas, andjinja2libraries - Access to CISA KEV catalog API and EPSS API from FIRST
- NVD API key (optional, for higher rate limits)
- Vulnerability scan results from tools like OpenVAS, Nessus, or Qualys
SSVC Decision Points
1. Exploitation Status
Assess current exploitation activity:
- None - No evidence of active exploitation
- PoC - Proof-of-concept exists publicly
- Active - Active exploitation observed in the wild (check CISA KEV)
# Check if a CVE is in CISA Known Exploited Vulnerabilities catalog
curl -s "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json" | \
python3 -c "import sys,json; data=json.load(sys.stdin); cves=[v['cveID'] for v in data['vulnerabilities']]; print('Active' if 'CVE-2024-3400' in cves else 'Check PoC/None')"2. Technical Impact
Determine scope of compromise if exploited:
- Partial - Limited to a subset of system functionality or data
- Total - Full control of the affected system, complete data access
3. Automatability
Evaluate if exploitation can be automated at scale:
- No - Requires manual, targeted exploitation per victim
- Yes - Can be scripted or worm-like propagation is possible
4. Mission Prevalence
How widespread is the affected product in your environment:
- Minimal - Limited deployment, non-critical systems
- Support - Supports mission-critical functions indirectly
- Essential - Directly enables core mission capabilities
5. Public Well-Being Impact
Potential consequences for physical safety and public welfare:
- Minimal - Negligible impact on safety or public services
- Material - Noticeable degradation of public services
- Irreversible - Loss of life, major property damage, or critical infrastructure failure
SSVC Decision Outcomes
| Outcome | Action Required | SLA |
|---|---|---|
| Track | Monitor, remediate in normal patch cycle | 90 days |
| Track* | Monitor closely, prioritize in next patch window | 60 days |
| Attend | Escalate to senior management, accelerate remediation | 14 days |
| Act | Apply mitigations immediately, executive-level awareness | 48 hours |
Workflow
Step 1: Ingest Vulnerability Data
import requests
import json
# Fetch CISA KEV catalog
kev_url = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
kev_data = requests.get(kev_url).json()
kev_cves = {v['cveID'] for v in kev_data['vulnerabilities']}
# Fetch EPSS scores for context
epss_url = "https://api.first.org/data/v1/epss"
epss_response = requests.get(epss_url, params={"cve": "CVE-2024-3400"}).json()Step 2: Evaluate Each Decision Point
def evaluate_exploitation(cve_id, kev_set):
"""Determine exploitation status from CISA KEV and EPSS data."""
if cve_id in kev_set:
return "active"
epss = requests.get(
"https://api.first.org/data/v1/epss",
params={"cve": cve_id}
).json()
if epss.get("data"):
score = float(epss["data"][0].get("epss", 0))
if score > 0.5:
return "poc"
return "none"
def evaluate_technical_impact(cvss_vector):
"""Parse CVSS vector for scope and impact metrics."""
if "S:C" in cvss_vector or "C:H/I:H/A:H" in cvss_vector:
return "total"
return "partial"
def evaluate_automatability(cvss_vector, cve_description):
"""Check if attack vector is network-based with low complexity."""
if "AV:N" in cvss_vector and "AC:L" in cvss_vector and "UI:N" in cvss_vector:
return "yes"
return "no"Step 3: Apply SSVC Decision Tree
def ssvc_decision(exploitation, tech_impact, automatability, mission_prevalence, public_wellbeing):
"""CISA SSVC decision tree implementation."""
if exploitation == "active":
if tech_impact == "total" or automatability == "yes":
return "Act"
if mission_prevalence in ("essential", "support"):
return "Act"
return "Attend"
if exploitation == "poc":
if automatability == "yes" and tech_impact == "total":
return "Attend"
if mission_prevalence == "essential":
return "Attend"
return "Track*"
# exploitation == "none"
if tech_impact == "total" and mission_prevalence == "essential":
return "Track*"
return "Track"Step 4: Generate Triage Report
# Run the SSVC triage script against scan results
python3 scripts/process.py --input scan_results.csv --output ssvc_triage_report.json
# View summary
cat ssvc_triage_report.json | python3 -m json.tool | head -50Integration with Vulnerability Scanners
Import from Nessus CSV
# Export Nessus scan as CSV, then process
python3 scripts/process.py \
--input nessus_export.csv \
--format nessus \
--output ssvc_results.jsonImport from OpenVAS
# Export OpenVAS results as XML
python3 scripts/process.py \
--input openvas_report.xml \
--format openvas \
--output ssvc_results.jsonValidation and Testing
# Test SSVC decision logic with known CVEs
python3 -c "
from scripts.process import ssvc_decision
# CVE-2024-3400 - Palo Alto PAN-OS command injection (KEV listed)
assert ssvc_decision('active', 'total', 'yes', 'essential', 'material') == 'Act'
# CVE-2024-21887 - Ivanti Connect Secure (PoC available)
assert ssvc_decision('poc', 'total', 'yes', 'support', 'minimal') == 'Attend'
print('All SSVC decision tests passed')
"References
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md2.1 KB
API Reference: Triaging Vulnerabilities with SSVC Framework
SSVC Decision Outcomes
| Decision | Action | Timeline |
|---|---|---|
| Act | Immediate remediation required | 24-48 hours |
| Attend | Urgent, prioritize in current cycle | 1-2 weeks |
| Track* | Monitor closely, schedule remediation | Next patch cycle |
| Track | Standard vulnerability management | Regular cadence |
SSVC Decision Points
| Decision Point | Values | Description |
|---|---|---|
| Exploitation | none, poc, active | Current exploitation activity |
| Technical Impact | partial, total | Scope of compromise if exploited |
| Automatability | no, yes | Can exploitation be automated? |
| Mission Prevalence | minimal, support, essential | Asset criticality to mission |
Enrichment APIs
| API | Endpoint | Purpose |
|---|---|---|
| CISA KEV | known_exploited_vulnerabilities.json |
Active exploitation check |
| FIRST EPSS | api.first.org/data/v1/epss?cve= |
Exploitation probability |
| NVD | services.nvd.nist.gov/rest/json/cves/2.0 |
CVSS scores, CWE |
Decision Tree Key Paths
| Exploitation | Impact | Automatability | Prevalence | Decision |
|---|---|---|---|---|
| Active | Total | any | any | Act |
| Active | Partial | Yes | any | Act |
| Active | Partial | No | Essential | Act |
| Active | Partial | No | Support | Attend |
| PoC | Total | Yes | any | Attend |
| PoC | Total | No | any | Track* |
| PoC | Partial | any | any | Track* |
| None | Total | any | any | Track* |
| None | Partial | any | any | Track |
Python Libraries
| Library | Version | Purpose |
|---|---|---|
requests |
>=2.28 | CISA KEV and EPSS API queries |
json |
stdlib | Report generation |
pathlib |
stdlib | Output directory management |
References
- CISA SSVC Guide: https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc
- SEI SSVC Paper: https://resources.sei.cmu.edu/library/asset-view.cfm?assetid=653459
- FIRST EPSS: https://www.first.org/epss/
- CISA KEV: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
standards.md3.2 KB
Standards and References - SSVC Vulnerability Triage
Primary Standards
CISA SSVC Framework
- Source: Cybersecurity and Infrastructure Security Agency (CISA)
- URL: https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc
- Version: SSVC v2.0 (2022 revision by CISA with SEI)
- Purpose: Provides a decision-tree methodology for vulnerability prioritization based on five decision points specific to the stakeholder's context
CERT/CC SSVC Original Research
- Source: Carnegie Mellon University Software Engineering Institute
- URL: https://certcc.github.io/SSVC/
- Publication: "Prioritizing Vulnerability Response: A Stakeholder-Specific Vulnerability Categorization" (2019)
- Authors: Jonathan Spring, Eric Hatleback, Allen Householder, Art Manion, Deana Shick
- DOI: https://doi.org/10.1184/R1/12124386
CVSS v3.1 and v4.0
- Source: Forum of Incident Response and Security Teams (FIRST)
- URL: https://www.first.org/cvss/
- CVSS v3.1 Specification: https://www.first.org/cvss/v3.1/specification-document
- CVSS v4.0 Specification: https://www.first.org/cvss/v4.0/specification-document
- Relevance: SSVC complements CVSS by adding contextual decision points beyond base score severity
EPSS - Exploit Prediction Scoring System
- Source: FIRST EPSS Special Interest Group
- URL: https://www.first.org/epss/
- API Endpoint: https://api.first.org/data/v1/epss
- Model Documentation: https://www.first.org/epss/model
- Relevance: EPSS probability scores inform the exploitation status decision point in SSVC
Regulatory and Compliance Context
CISA Binding Operational Directive 22-01
- Title: Reducing the Significant Risk of Known Exploited Vulnerabilities
- URL: https://www.cisa.gov/binding-operational-directive-22-01
- Relevance: Mandates federal agencies to remediate KEV-listed vulnerabilities within specified timeframes; SSVC aligns remediation priorities with BOD 22-01 requirements
NIST SP 800-40 Rev 4
- Title: Guide to Enterprise Patch Management Planning
- URL: https://csrc.nist.gov/publications/detail/sp/800-40/rev-4/final
- Relevance: Provides organizational context for patch management decisions that SSVC informs
NIST Cybersecurity Framework (CSF) 2.0
- Function: IDENTIFY (ID.RA - Risk Assessment)
- URL: https://www.nist.gov/cyberframework
- Relevance: SSVC directly supports the risk assessment category for vulnerability prioritization
Data Sources
CISA Known Exploited Vulnerabilities (KEV) Catalog
- URL: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
- JSON Feed: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
- Update Frequency: Updated as new exploited vulnerabilities are confirmed
National Vulnerability Database (NVD)
- URL: https://nvd.nist.gov/
- API v2: https://services.nvd.nist.gov/rest/json/cves/2.0
- Relevance: Provides CVSS scores and vulnerability details used in SSVC decision points
MITRE CVE Program
- URL: https://cve.mitre.org/
- CVE List: https://www.cve.org/
- Relevance: CVE identifiers are the primary key for linking vulnerability data across SSVC decision points
workflows.md4.2 KB
Workflows - SSVC Vulnerability Triage
Workflow 1: Initial SSVC Triage Pipeline
Trigger
New vulnerability scan results imported from Nessus, Qualys, OpenVAS, or other scanner.
Steps
-
Ingest Scan Results
- Parse scanner output (CSV, XML, or JSON format)
- Extract CVE identifiers, affected hosts, CVSS vectors, and descriptions
- Deduplicate findings by CVE + host combination
-
Enrich with External Intelligence
- Query CISA KEV catalog JSON feed for exploitation status
- Query FIRST EPSS API for exploitation probability scores
- Query NVD API v2 for CVSS v3.1/v4.0 vectors and CWE mappings
- Cache API responses to avoid rate limiting (NVD: 5 requests/30s without key, 50/30s with key)
-
Evaluate SSVC Decision Points
- Exploitation: Map KEV membership to "Active", EPSS > 0.5 to "PoC", otherwise "None"
- Technical Impact: Parse CVSS vector; if Scope:Changed or CIA all High, mark "Total"
- Automatability: Network vector + Low complexity + No user interaction = "Yes"
- Mission Prevalence: Cross-reference affected assets with CMDB criticality tags
- Public Well-Being: Map asset function to safety impact categories
-
Apply Decision Tree
- Walk the CISA SSVC decision tree with evaluated decision points
- Assign outcome: Track, Track*, Attend, or Act
-
Generate Prioritized Report
- Sort vulnerabilities by SSVC outcome (Act > Attend > Track* > Track)
- Within each category, secondary sort by EPSS score descending
- Output JSON report and CSV summary for ticketing integration
Workflow 2: Continuous SSVC Monitoring
Trigger
Daily scheduled job (cron or CI/CD pipeline).
Steps
-
Refresh CISA KEV Catalog
curl -s -o /tmp/kev_catalog.json \ "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json" -
Check Previously Tracked CVEs Against Updated KEV
- Compare current open vulnerabilities against latest KEV additions
- If a previously "Track" or "Track*" CVE appears in KEV, re-evaluate to "Attend" or "Act"
-
Refresh EPSS Scores
curl -s "https://api.first.org/data/v1/epss?cve=CVE-2024-3400,CVE-2024-21887" | \ python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin)['data'], indent=2))" -
Update SSVC Outcomes
- Re-run decision tree for all open vulnerabilities with refreshed data
- Flag any outcome changes (e.g., Track -> Attend)
-
Send Notifications
- Slack/Teams webhook for any new "Act" or "Attend" outcomes
- Email digest for "Track*" changes
- Update Jira/ServiceNow tickets with new SSVC classification
Workflow 3: Asset-Context SSVC Enrichment
Trigger
New asset onboarded or asset criticality classification updated.
Steps
-
Import Asset Inventory
- Pull from CMDB (ServiceNow, Snipe-IT, or similar)
- Map each asset to mission prevalence category:
- Minimal: development, test environments
- Support: backup systems, monitoring infrastructure
- Essential: production databases, authentication servers, customer-facing apps
-
Map Public Well-Being Impact
- Healthcare systems, SCADA/ICS, transportation: Irreversible
- Public web services, financial processing: Material
- Internal tools, development systems: Minimal
-
Re-Evaluate Open Vulnerabilities
- Apply updated asset context to all open vulnerability SSVC evaluations
- Generate delta report showing outcome changes
Workflow 4: SSVC Metrics and Reporting
Trigger
Weekly/monthly reporting cycle.
Metrics to Track
| Metric | Calculation | Target |
|---|---|---|
| Mean Time to Remediate (Act) | Avg days from Act classification to closure | < 2 days |
| Mean Time to Remediate (Attend) | Avg days from Attend classification to closure | < 14 days |
| SLA Breach Rate | % of vulns not remediated within SLA | < 5% |
| Act Backlog | Count of open Act-classified vulnerabilities | 0 |
| Attend Backlog | Count of open Attend-classified vulnerabilities | < 10 |
| Coverage Rate | % of vulnerabilities processed through SSVC | > 95% |
Report Generation
python3 scripts/process.py \
--mode report \
--input ssvc_results.json \
--period weekly \
--output ssvc_metrics_report.htmlScripts 2
agent.py8.1 KB
#!/usr/bin/env python3
"""Agent for triaging vulnerabilities with the SSVC framework.
Implements CISA's Stakeholder-Specific Vulnerability Categorization
decision tree to produce actionable priorities: Track, Track*,
Attend, or Act based on exploitation status, technical impact,
automatability, and mission prevalence.
"""
import json
import sys
from pathlib import Path
from datetime import datetime
try:
import requests
except ImportError:
requests = None
class ExploitationStatus:
NONE = "none"
POC = "poc"
ACTIVE = "active"
class TechnicalImpact:
PARTIAL = "partial"
TOTAL = "total"
class Automatability:
NO = "no"
YES = "yes"
class MissionPrevalence:
MINIMAL = "minimal"
SUPPORT = "support"
ESSENTIAL = "essential"
class SSVCDecision:
TRACK = "Track"
TRACK_STAR = "Track*"
ATTEND = "Attend"
ACT = "Act"
SSVC_DECISION_TREE = {
(ExploitationStatus.ACTIVE, TechnicalImpact.TOTAL): SSVCDecision.ACT,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.YES): SSVCDecision.ACT,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.ESSENTIAL): SSVCDecision.ACT,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.SUPPORT): SSVCDecision.ATTEND,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.MINIMAL): SSVCDecision.ATTEND,
(ExploitationStatus.POC, TechnicalImpact.TOTAL, Automatability.YES): SSVCDecision.ATTEND,
(ExploitationStatus.POC, TechnicalImpact.TOTAL, Automatability.NO): SSVCDecision.TRACK_STAR,
(ExploitationStatus.POC, TechnicalImpact.PARTIAL): SSVCDecision.TRACK_STAR,
(ExploitationStatus.NONE, TechnicalImpact.TOTAL): SSVCDecision.TRACK_STAR,
(ExploitationStatus.NONE, TechnicalImpact.PARTIAL): SSVCDecision.TRACK,
}
class SSVCTriageAgent:
"""Triages vulnerabilities using the SSVC decision tree."""
def __init__(self, output_dir="./ssvc_triage"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.results = []
def check_cisa_kev(self, cve_id):
"""Check if CVE is in CISA Known Exploited Vulnerabilities catalog."""
if not requests:
return None
resp = None
try:
resp = requests.get(
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
timeout=15,
)
except Exception:
return None
if resp and resp.status_code == 200:
data = resp.json()
for vuln in data.get("vulnerabilities", []):
if vuln.get("cveID") == cve_id:
return {
"in_kev": True,
"vendor": vuln.get("vendorProject"),
"product": vuln.get("product"),
"date_added": vuln.get("dateAdded"),
"due_date": vuln.get("dueDate"),
}
return {"in_kev": False}
def get_epss_score(self, cve_id):
"""Get EPSS probability score from FIRST API."""
if not requests:
return None
try:
resp = requests.get(f"https://api.first.org/data/v1/epss?cve={cve_id}", timeout=10)
if resp and resp.status_code == 200:
data = resp.json().get("data", [])
if data:
return {
"cve": cve_id,
"epss": float(data[0].get("epss", 0)),
"percentile": float(data[0].get("percentile", 0)),
}
except Exception:
pass
return None
def determine_exploitation(self, cve_id):
"""Determine exploitation status using KEV and EPSS."""
kev = self.check_cisa_kev(cve_id)
if kev and kev.get("in_kev"):
return ExploitationStatus.ACTIVE, kev
epss = self.get_epss_score(cve_id)
if epss and epss.get("epss", 0) > 0.5:
return ExploitationStatus.POC, epss
if epss and epss.get("epss", 0) > 0.1:
return ExploitationStatus.POC, epss
return ExploitationStatus.NONE, epss
def evaluate_decision(self, exploitation, technical_impact, automatability=None,
mission_prevalence=None):
"""Walk the SSVC decision tree to produce a prioritization."""
if (exploitation, technical_impact) in SSVC_DECISION_TREE:
return SSVC_DECISION_TREE[(exploitation, technical_impact)]
if automatability:
key = (exploitation, technical_impact, automatability)
if key in SSVC_DECISION_TREE:
return SSVC_DECISION_TREE[key]
if mission_prevalence:
key = (exploitation, technical_impact, automatability, mission_prevalence)
if key in SSVC_DECISION_TREE:
return SSVC_DECISION_TREE[key]
return SSVCDecision.TRACK
def triage_cve(self, cve_id, technical_impact=TechnicalImpact.PARTIAL,
automatability=Automatability.NO,
mission_prevalence=MissionPrevalence.SUPPORT):
"""Full SSVC triage for a single CVE."""
exploitation, enrichment = self.determine_exploitation(cve_id)
decision = self.evaluate_decision(exploitation, technical_impact,
automatability, mission_prevalence)
result = {
"cve_id": cve_id,
"exploitation_status": exploitation,
"technical_impact": technical_impact,
"automatability": automatability,
"mission_prevalence": mission_prevalence,
"decision": decision,
"enrichment": enrichment,
"remediation_timeline": self._get_timeline(decision),
}
self.results.append(result)
return result
def _get_timeline(self, decision):
timelines = {
SSVCDecision.ACT: "Immediate - remediate within 24-48 hours",
SSVCDecision.ATTEND: "Urgent - remediate within 1-2 weeks",
SSVCDecision.TRACK_STAR: "Scheduled - remediate in next patch cycle",
SSVCDecision.TRACK: "Monitor - include in regular vulnerability management",
}
return timelines.get(decision, "Unknown")
def triage_batch(self, cves, defaults=None):
"""Triage a list of CVEs with optional default parameters."""
defaults = defaults or {}
for cve in cves:
self.triage_cve(
cve,
technical_impact=defaults.get("technical_impact", TechnicalImpact.PARTIAL),
automatability=defaults.get("automatability", Automatability.NO),
mission_prevalence=defaults.get("mission_prevalence", MissionPrevalence.SUPPORT),
)
return self.results
def generate_report(self, cves=None):
if cves:
self.triage_batch(cves)
by_decision = {}
for r in self.results:
d = r["decision"]
by_decision[d] = by_decision.get(d, 0) + 1
report = {
"report_date": datetime.utcnow().isoformat(),
"framework": "SSVC (CISA Stakeholder-Specific Vulnerability Categorization)",
"total_triaged": len(self.results),
"by_decision": by_decision,
"triage_results": self.results,
}
out = self.output_dir / "ssvc_triage_report.json"
with open(out, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 2:
print("Usage: agent.py <CVE-ID> [CVE-ID2 ...] [--impact total|partial]")
sys.exit(1)
cves = [a for a in sys.argv[1:] if a.startswith("CVE-")]
impact = TechnicalImpact.PARTIAL
if "--impact" in sys.argv:
val = sys.argv[sys.argv.index("--impact") + 1]
impact = TechnicalImpact.TOTAL if val == "total" else TechnicalImpact.PARTIAL
agent = SSVCTriageAgent()
agent.generate_report(cves)
if __name__ == "__main__":
main()
process.py11.9 KB
#!/usr/bin/env python3
"""SSVC Vulnerability Triage Processor.
Evaluates vulnerabilities against CISA's Stakeholder-Specific Vulnerability
Categorization (SSVC) decision tree and produces prioritized triage reports.
"""
import argparse
import csv
import json
import sys
import time
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from pathlib import Path
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"
SSVC_SLA = {
"Act": 2,
"Attend": 14,
"Track*": 60,
"Track": 90,
}
def fetch_kev_catalog():
"""Download the CISA Known Exploited Vulnerabilities catalog."""
resp = requests.get(KEV_URL, timeout=30)
resp.raise_for_status()
data = resp.json()
return {v["cveID"] for v in data.get("vulnerabilities", [])}
def fetch_epss_scores(cve_ids):
"""Fetch EPSS scores for a list of CVE IDs from FIRST API."""
scores = {}
batch_size = 100
for i in range(0, len(cve_ids), batch_size):
batch = cve_ids[i : i + batch_size]
params = {"cve": ",".join(batch)}
resp = requests.get(EPSS_API, params=params, timeout=30)
if resp.status_code == 200:
for entry in resp.json().get("data", []):
scores[entry["cve"]] = {
"epss": float(entry.get("epss", 0)),
"percentile": float(entry.get("percentile", 0)),
}
time.sleep(1)
return scores
def fetch_nvd_cve(cve_id, api_key=None):
"""Fetch CVE details from NVD API v2."""
params = {"cveId": cve_id}
headers = {}
if api_key:
headers["apiKey"] = api_key
resp = requests.get(NVD_API, params=params, headers=headers, timeout=30)
if resp.status_code == 200:
vulns = resp.json().get("vulnerabilities", [])
if vulns:
return vulns[0].get("cve", {})
return None
def evaluate_exploitation(cve_id, kev_set, epss_scores):
"""Determine exploitation status: active, poc, or none."""
if cve_id in kev_set:
return "active"
epss_data = epss_scores.get(cve_id, {})
if epss_data.get("epss", 0) > 0.5:
return "poc"
if epss_data.get("epss", 0) > 0.1:
return "poc"
return "none"
def evaluate_technical_impact(cvss_vector):
"""Assess technical impact from CVSS vector string."""
if not cvss_vector:
return "partial"
vector_upper = cvss_vector.upper()
if "S:C" in vector_upper:
return "total"
if "C:H" in vector_upper and "I:H" in vector_upper and "A:H" in vector_upper:
return "total"
if "C:H" in vector_upper and "I:H" in vector_upper:
return "total"
return "partial"
def evaluate_automatability(cvss_vector):
"""Determine if exploitation can be automated."""
if not cvss_vector:
return "no"
vector_upper = cvss_vector.upper()
network = "AV:N" in vector_upper
low_complexity = "AC:L" in vector_upper
no_user_interaction = "UI:N" in vector_upper
if network and low_complexity and no_user_interaction:
return "yes"
return "no"
def ssvc_decision(exploitation, tech_impact, automatability, mission_prevalence, public_wellbeing):
"""Apply CISA SSVC decision tree to produce triage outcome.
Returns one of: Act, Attend, Track*, Track
"""
if exploitation == "active":
if automatability == "yes":
return "Act"
if tech_impact == "total":
if mission_prevalence in ("essential", "support"):
return "Act"
return "Attend"
if mission_prevalence == "essential":
return "Attend"
if public_wellbeing in ("irreversible", "material"):
return "Attend"
return "Attend"
if exploitation == "poc":
if automatability == "yes" and tech_impact == "total":
if mission_prevalence in ("essential", "support"):
return "Attend"
return "Track*"
if tech_impact == "total" and mission_prevalence == "essential":
return "Attend"
if public_wellbeing == "irreversible":
return "Attend"
return "Track*"
# exploitation == "none"
if tech_impact == "total" and mission_prevalence == "essential":
return "Track*"
if automatability == "yes" and mission_prevalence == "essential":
return "Track*"
return "Track"
def parse_nessus_csv(filepath):
"""Parse Nessus CSV export into vulnerability records."""
vulns = []
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
cve = row.get("CVE", "").strip()
if not cve or not cve.startswith("CVE-"):
continue
vulns.append(
{
"cve_id": cve,
"host": row.get("Host", "unknown"),
"port": row.get("Port", ""),
"plugin_name": row.get("Name", ""),
"severity": row.get("Severity", ""),
"cvss_vector": row.get("CVSS V3 Vector", ""),
"description": row.get("Synopsis", ""),
}
)
return vulns
def parse_openvas_xml(filepath):
"""Parse OpenVAS XML report into vulnerability records."""
vulns = []
tree = ET.parse(filepath)
root = tree.getroot()
for result in root.iter("result"):
nvt = result.find("nvt")
if nvt is None:
continue
cve_elem = nvt.find("cve")
if cve_elem is None or not cve_elem.text or cve_elem.text == "NOCVE":
continue
host_elem = result.find("host")
port_elem = result.find("port")
vulns.append(
{
"cve_id": cve_elem.text.strip(),
"host": host_elem.text.strip() if host_elem is not None else "unknown",
"port": port_elem.text.strip() if port_elem is not None else "",
"plugin_name": nvt.findtext("name", ""),
"severity": result.findtext("severity", ""),
"cvss_vector": nvt.findtext("tags", ""),
"description": result.findtext("description", ""),
}
)
return vulns
def parse_generic_csv(filepath):
"""Parse generic CSV with cve_id, host, cvss_vector columns."""
vulns = []
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
cve = row.get("cve_id", "").strip()
if not cve:
continue
vulns.append(
{
"cve_id": cve,
"host": row.get("host", "unknown"),
"port": row.get("port", ""),
"plugin_name": row.get("plugin_name", ""),
"severity": row.get("severity", ""),
"cvss_vector": row.get("cvss_vector", ""),
"description": row.get("description", ""),
"mission_prevalence": row.get("mission_prevalence", "support"),
"public_wellbeing": row.get("public_wellbeing", "minimal"),
}
)
return vulns
def run_triage(vulns, kev_set, epss_scores, default_mission="support", default_wellbeing="minimal"):
"""Run SSVC triage on a list of vulnerability records."""
results = []
for vuln in vulns:
cve_id = vuln["cve_id"]
exploitation = evaluate_exploitation(cve_id, kev_set, epss_scores)
tech_impact = evaluate_technical_impact(vuln.get("cvss_vector", ""))
automatability = evaluate_automatability(vuln.get("cvss_vector", ""))
mission = vuln.get("mission_prevalence", default_mission)
wellbeing = vuln.get("public_wellbeing", default_wellbeing)
outcome = ssvc_decision(exploitation, tech_impact, automatability, mission, wellbeing)
epss_data = epss_scores.get(cve_id, {})
results.append(
{
"cve_id": cve_id,
"host": vuln.get("host", "unknown"),
"port": vuln.get("port", ""),
"plugin_name": vuln.get("plugin_name", ""),
"ssvc_outcome": outcome,
"sla_days": SSVC_SLA[outcome],
"exploitation_status": exploitation,
"technical_impact": tech_impact,
"automatability": automatability,
"mission_prevalence": mission,
"public_wellbeing": wellbeing,
"epss_score": epss_data.get("epss", 0),
"epss_percentile": epss_data.get("percentile", 0),
"in_kev": cve_id in kev_set,
}
)
outcome_order = {"Act": 0, "Attend": 1, "Track*": 2, "Track": 3}
results.sort(key=lambda r: (outcome_order.get(r["ssvc_outcome"], 4), -r["epss_score"]))
return results
def generate_report(results, output_path, report_format="json"):
"""Generate triage report in JSON or CSV format."""
summary = {
"generated_at": datetime.now(timezone.utc).isoformat(),
"total_vulnerabilities": len(results),
"outcome_counts": {},
"results": results,
}
for r in results:
outcome = r["ssvc_outcome"]
summary["outcome_counts"][outcome] = summary["outcome_counts"].get(outcome, 0) + 1
if report_format == "csv":
if results:
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=results[0].keys())
writer.writeheader()
writer.writerows(results)
else:
with open(output_path, "w", encoding="utf-8") as f:
json.dump(summary, f, indent=2)
return summary
def main():
parser = argparse.ArgumentParser(description="SSVC Vulnerability Triage Processor")
parser.add_argument("--input", required=True, help="Path to vulnerability scan results")
parser.add_argument("--output", default="ssvc_triage_report.json", help="Output report path")
parser.add_argument(
"--format",
choices=["nessus", "openvas", "generic"],
default="generic",
help="Input format",
)
parser.add_argument(
"--output-format", choices=["json", "csv"], default="json", help="Output format"
)
parser.add_argument("--nvd-api-key", help="NVD API key for higher rate limits")
parser.add_argument(
"--mission-prevalence",
choices=["minimal", "support", "essential"],
default="support",
help="Default mission prevalence",
)
parser.add_argument(
"--public-wellbeing",
choices=["minimal", "material", "irreversible"],
default="minimal",
help="Default public well-being impact",
)
args = parser.parse_args()
print("[*] Fetching CISA KEV catalog...")
kev_set = fetch_kev_catalog()
print(f" Loaded {len(kev_set)} known exploited vulnerabilities")
print(f"[*] Parsing input file: {args.input}")
if args.format == "nessus":
vulns = parse_nessus_csv(args.input)
elif args.format == "openvas":
vulns = parse_openvas_xml(args.input)
else:
vulns = parse_generic_csv(args.input)
print(f" Found {len(vulns)} vulnerability records")
cve_ids = list({v["cve_id"] for v in vulns})
print(f"[*] Fetching EPSS scores for {len(cve_ids)} unique CVEs...")
epss_scores = fetch_epss_scores(cve_ids)
print("[*] Running SSVC triage...")
results = run_triage(
vulns, kev_set, epss_scores, args.mission_prevalence, args.public_wellbeing
)
print(f"[*] Generating report: {args.output}")
summary = generate_report(results, args.output, args.output_format)
print("\n[+] SSVC Triage Summary:")
for outcome, count in sorted(summary["outcome_counts"].items()):
print(f" {outcome}: {count}")
print(f" Total: {summary['total_vulnerabilities']}")
if __name__ == "__main__":
main()