chore: import upstream snapshot with attribution
This commit is contained in:
+392
@@ -0,0 +1,392 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Risk Assessment Calculator
|
||||
|
||||
Calculates risk scores using both qualitative and quantitative methodologies.
|
||||
Supports risk matrix, ALE calculations, and cost-benefit analysis for controls.
|
||||
|
||||
Usage:
|
||||
python risk_calculator.py --interactive
|
||||
python risk_calculator.py risks.csv
|
||||
python risk_calculator.py risks.csv --output risk_report.csv
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Risk:
|
||||
"""Risk assessment data class"""
|
||||
id: str
|
||||
name: str
|
||||
asset_value: float
|
||||
exposure_factor: float
|
||||
aro: float # Annualized Rate of Occurrence
|
||||
likelihood_qualitative: int # 1-5 scale
|
||||
impact_qualitative: int # 1-5 scale
|
||||
category: str
|
||||
owner: str
|
||||
|
||||
|
||||
class RiskCalculator:
|
||||
"""Risk assessment calculator with multiple methodologies"""
|
||||
|
||||
# Risk matrix: (likelihood, impact) -> risk_level
|
||||
RISK_MATRIX = {
|
||||
(1, 1): "Low", (1, 2): "Low", (1, 3): "Low", (1, 4): "Medium", (1, 5): "Medium",
|
||||
(2, 1): "Low", (2, 2): "Low", (2, 3): "Medium", (2, 4): "High", (2, 5): "High",
|
||||
(3, 1): "Low", (3, 2): "Medium", (3, 3): "Medium", (3, 4): "High", (3, 5): "Critical",
|
||||
(4, 1): "Medium", (4, 2): "High", (4, 3): "High", (4, 4): "Critical", (4, 5): "Critical",
|
||||
(5, 1): "Medium", (5, 2): "High", (5, 3): "Critical", (5, 4): "Critical", (5, 5): "Critical"
|
||||
}
|
||||
|
||||
SLA_DAYS = {
|
||||
"Critical": 1,
|
||||
"High": 7,
|
||||
"Medium": 30,
|
||||
"Low": 90
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.risks: List[Risk] = []
|
||||
|
||||
def calculate_quantitative(self, risk: Risk) -> Dict:
|
||||
"""Calculate quantitative risk metrics (SLE, ALE)"""
|
||||
sle = risk.asset_value * risk.exposure_factor
|
||||
ale = sle * risk.aro
|
||||
|
||||
return {
|
||||
"sle": round(sle, 2),
|
||||
"ale": round(ale, 2)
|
||||
}
|
||||
|
||||
def calculate_qualitative(self, risk: Risk) -> Dict:
|
||||
"""Calculate qualitative risk metrics"""
|
||||
risk_score = risk.likelihood_qualitative * risk.impact_qualitative
|
||||
risk_level = self.RISK_MATRIX.get(
|
||||
(risk.likelihood_qualitative, risk.impact_qualitative),
|
||||
"Unknown"
|
||||
)
|
||||
sla_days = self.SLA_DAYS.get(risk_level, 90)
|
||||
|
||||
return {
|
||||
"risk_score": risk_score,
|
||||
"risk_level": risk_level,
|
||||
"sla_days": sla_days
|
||||
}
|
||||
|
||||
def cost_benefit_analysis(self, risk: Risk, control_cost: float, new_aro: float) -> Dict:
|
||||
"""Perform cost-benefit analysis for a security control"""
|
||||
quant = self.calculate_quantitative(risk)
|
||||
ale_before = quant["ale"]
|
||||
|
||||
# Calculate ALE after control
|
||||
ale_after = (risk.asset_value * risk.exposure_factor) * new_aro
|
||||
|
||||
annual_savings = ale_before - ale_after
|
||||
net_benefit = annual_savings - control_cost
|
||||
|
||||
roi = (net_benefit / control_cost * 100) if control_cost > 0 else 0
|
||||
|
||||
return {
|
||||
"ale_before": round(ale_before, 2),
|
||||
"ale_after": round(ale_after, 2),
|
||||
"annual_savings": round(annual_savings, 2),
|
||||
"control_cost": control_cost,
|
||||
"net_benefit": round(net_benefit, 2),
|
||||
"roi_percent": round(roi, 2),
|
||||
"recommendation": "Implement" if net_benefit > 0 else "Do not implement",
|
||||
"payback_period_years": round(control_cost / annual_savings, 2) if annual_savings > 0 else float('inf')
|
||||
}
|
||||
|
||||
def add_risk(self, risk: Risk):
|
||||
"""Add risk to assessment"""
|
||||
self.risks.append(risk)
|
||||
|
||||
def generate_report(self) -> List[Dict]:
|
||||
"""Generate comprehensive risk report"""
|
||||
report = []
|
||||
|
||||
for risk in self.risks:
|
||||
quant = self.calculate_quantitative(risk)
|
||||
qual = self.calculate_qualitative(risk)
|
||||
|
||||
report.append({
|
||||
"Risk ID": risk.id,
|
||||
"Risk Name": risk.name,
|
||||
"Category": risk.category,
|
||||
"Owner": risk.owner,
|
||||
"Asset Value": f"${risk.asset_value:,.0f}",
|
||||
"Exposure Factor": f"{risk.exposure_factor:.0%}",
|
||||
"ARO": f"{risk.aro:.2f}",
|
||||
"SLE": f"${quant['sle']:,.0f}",
|
||||
"ALE": f"${quant['ale']:,.0f}",
|
||||
"Likelihood": risk.likelihood_qualitative,
|
||||
"Impact": risk.impact_qualitative,
|
||||
"Risk Score": qual["risk_score"],
|
||||
"Risk Level": qual["risk_level"],
|
||||
"Remediation SLA": f"{qual['sla_days']} days"
|
||||
})
|
||||
|
||||
# Sort by ALE (descending)
|
||||
report.sort(key=lambda x: float(x["ALE"].replace("$", "").replace(",", "")), reverse=True)
|
||||
|
||||
return report
|
||||
|
||||
def generate_summary(self) -> Dict:
|
||||
"""Generate summary statistics"""
|
||||
if not self.risks:
|
||||
return {}
|
||||
|
||||
total_ale = sum(self.calculate_quantitative(r)["ale"] for r in self.risks)
|
||||
|
||||
risk_levels = {"Critical": 0, "High": 0, "Medium": 0, "Low": 0}
|
||||
for risk in self.risks:
|
||||
qual = self.calculate_qualitative(risk)
|
||||
risk_levels[qual["risk_level"]] = risk_levels.get(qual["risk_level"], 0) + 1
|
||||
|
||||
top_risks = sorted(
|
||||
[(r, self.calculate_quantitative(r)["ale"]) for r in self.risks],
|
||||
key=lambda x: x[1],
|
||||
reverse=True
|
||||
)[:5]
|
||||
|
||||
return {
|
||||
"total_risks": len(self.risks),
|
||||
"total_ale": round(total_ale, 2),
|
||||
"risk_levels": risk_levels,
|
||||
"top_5_risks": [(r.name, round(ale, 2)) for r, ale in top_risks]
|
||||
}
|
||||
|
||||
|
||||
def load_risks_from_csv(filename: str) -> List[Risk]:
|
||||
"""Load risks from CSV file"""
|
||||
risks = []
|
||||
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
risk = Risk(
|
||||
id=row['id'],
|
||||
name=row['name'],
|
||||
asset_value=float(row['asset_value']),
|
||||
exposure_factor=float(row['exposure_factor']),
|
||||
aro=float(row['aro']),
|
||||
likelihood_qualitative=int(row['likelihood']),
|
||||
impact_qualitative=int(row['impact']),
|
||||
category=row['category'],
|
||||
owner=row['owner']
|
||||
)
|
||||
risks.append(risk)
|
||||
|
||||
return risks
|
||||
|
||||
|
||||
def save_report_to_csv(report: List[Dict], filename: str):
|
||||
"""Save risk report to CSV file"""
|
||||
if not report:
|
||||
print("No data to save")
|
||||
return
|
||||
|
||||
with open(filename, 'w', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=report[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(report)
|
||||
|
||||
print(f"Report saved to {filename}")
|
||||
|
||||
|
||||
def interactive_mode():
|
||||
"""Interactive risk assessment mode"""
|
||||
calculator = RiskCalculator()
|
||||
|
||||
print("=" * 60)
|
||||
print("Risk Assessment Calculator - Interactive Mode")
|
||||
print("=" * 60)
|
||||
|
||||
while True:
|
||||
print("\nOptions:")
|
||||
print("1. Add new risk")
|
||||
print("2. Calculate cost-benefit for control")
|
||||
print("3. Generate risk report")
|
||||
print("4. View summary")
|
||||
print("5. Exit")
|
||||
|
||||
choice = input("\nEnter choice (1-5): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
print("\n--- Add New Risk ---")
|
||||
risk_id = input("Risk ID: ").strip()
|
||||
name = input("Risk Name: ").strip()
|
||||
asset_value = float(input("Asset Value ($): "))
|
||||
exposure_factor = float(input("Exposure Factor (0-1): "))
|
||||
aro = float(input("Annual Rate of Occurrence (0-1): "))
|
||||
likelihood = int(input("Likelihood (1-5): "))
|
||||
impact = int(input("Impact (1-5): "))
|
||||
category = input("Category: ").strip()
|
||||
owner = input("Owner: ").strip()
|
||||
|
||||
risk = Risk(risk_id, name, asset_value, exposure_factor, aro,
|
||||
likelihood, impact, category, owner)
|
||||
calculator.add_risk(risk)
|
||||
|
||||
quant = calculator.calculate_quantitative(risk)
|
||||
qual = calculator.calculate_qualitative(risk)
|
||||
|
||||
print(f"\n✓ Risk added successfully!")
|
||||
print(f" SLE: ${quant['sle']:,.0f}")
|
||||
print(f" ALE: ${quant['ale']:,.0f}")
|
||||
print(f" Risk Level: {qual['risk_level']}")
|
||||
print(f" Remediation SLA: {qual['sla_days']} days")
|
||||
|
||||
elif choice == "2":
|
||||
if not calculator.risks:
|
||||
print("No risks added yet. Please add a risk first.")
|
||||
continue
|
||||
|
||||
print("\n--- Cost-Benefit Analysis ---")
|
||||
print("Available risks:")
|
||||
for i, risk in enumerate(calculator.risks, 1):
|
||||
print(f"{i}. {risk.name} (ID: {risk.id})")
|
||||
|
||||
risk_idx = int(input("Select risk number: ")) - 1
|
||||
if risk_idx < 0 or risk_idx >= len(calculator.risks):
|
||||
print("Invalid selection")
|
||||
continue
|
||||
|
||||
risk = calculator.risks[risk_idx]
|
||||
control_cost = float(input("Annual cost of control ($): "))
|
||||
new_aro = float(input("New ARO after control (0-1): "))
|
||||
|
||||
cba = calculator.cost_benefit_analysis(risk, control_cost, new_aro)
|
||||
|
||||
print(f"\n--- Cost-Benefit Analysis Results ---")
|
||||
print(f"ALE Before Control: ${cba['ale_before']:,.0f}")
|
||||
print(f"ALE After Control: ${cba['ale_after']:,.0f}")
|
||||
print(f"Annual Savings: ${cba['annual_savings']:,.0f}")
|
||||
print(f"Control Cost: ${cba['control_cost']:,.0f}")
|
||||
print(f"Net Benefit: ${cba['net_benefit']:,.0f}")
|
||||
print(f"ROI: {cba['roi_percent']:.1f}%")
|
||||
print(f"Payback Period: {cba['payback_period_years']:.2f} years")
|
||||
print(f"Recommendation: {cba['recommendation']}")
|
||||
|
||||
elif choice == "3":
|
||||
if not calculator.risks:
|
||||
print("No risks added yet. Please add a risk first.")
|
||||
continue
|
||||
|
||||
report = calculator.generate_report()
|
||||
print("\n" + "=" * 120)
|
||||
print("Risk Assessment Report")
|
||||
print("=" * 120)
|
||||
|
||||
# Print header
|
||||
headers = list(report[0].keys())
|
||||
print("|".join(f"{h:^15}" for h in headers))
|
||||
print("-" * 120)
|
||||
|
||||
# Print rows
|
||||
for row in report:
|
||||
print("|".join(f"{str(v):^15}" for v in row.values()))
|
||||
|
||||
elif choice == "4":
|
||||
summary = calculator.generate_summary()
|
||||
if not summary:
|
||||
print("No risks added yet. Please add a risk first.")
|
||||
continue
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Risk Assessment Summary")
|
||||
print("=" * 60)
|
||||
print(f"Total Risks: {summary['total_risks']}")
|
||||
print(f"Total ALE: ${summary['total_ale']:,.0f}")
|
||||
print(f"\nRisk Level Distribution:")
|
||||
for level, count in summary['risk_levels'].items():
|
||||
print(f" {level}: {count}")
|
||||
print(f"\nTop 5 Risks by ALE:")
|
||||
for name, ale in summary['top_5_risks']:
|
||||
print(f" {name}: ${ale:,.0f}")
|
||||
|
||||
elif choice == "5":
|
||||
print("Exiting...")
|
||||
break
|
||||
|
||||
else:
|
||||
print("Invalid choice. Please enter 1-5.")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Risk Assessment Calculator")
|
||||
parser.add_argument('input_file', nargs='?', help='CSV file containing risk data')
|
||||
parser.add_argument('--output', '-o', help='Output CSV file for risk report')
|
||||
parser.add_argument('--interactive', '-i', action='store_true',
|
||||
help='Run in interactive mode')
|
||||
parser.add_argument('--control-cost', type=float,
|
||||
help='Cost of control for cost-benefit analysis')
|
||||
parser.add_argument('--new-aro', type=float,
|
||||
help='New ARO after control implementation')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.interactive:
|
||||
interactive_mode()
|
||||
return
|
||||
|
||||
if not args.input_file:
|
||||
print("Error: Please provide an input file or use --interactive mode")
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
# Load risks from CSV
|
||||
try:
|
||||
risks = load_risks_from_csv(args.input_file)
|
||||
calculator = RiskCalculator()
|
||||
|
||||
for risk in risks:
|
||||
calculator.add_risk(risk)
|
||||
|
||||
print(f"Loaded {len(risks)} risks from {args.input_file}")
|
||||
|
||||
# Generate report
|
||||
report = calculator.generate_report()
|
||||
|
||||
# Display summary
|
||||
summary = calculator.generate_summary()
|
||||
print("\n" + "=" * 60)
|
||||
print("Risk Assessment Summary")
|
||||
print("=" * 60)
|
||||
print(f"Total Risks: {summary['total_risks']}")
|
||||
print(f"Total ALE: ${summary['total_ale']:,.0f}")
|
||||
print(f"\nRisk Level Distribution:")
|
||||
for level, count in summary['risk_levels'].items():
|
||||
if count > 0:
|
||||
print(f" {level}: {count}")
|
||||
|
||||
print(f"\nTop 5 Risks by ALE:")
|
||||
for name, ale in summary['top_5_risks']:
|
||||
print(f" {name}: ${ale:,.0f}")
|
||||
|
||||
# Save report if output file specified
|
||||
if args.output:
|
||||
save_report_to_csv(report, args.output)
|
||||
|
||||
print("\nRisk Report:")
|
||||
print("-" * 120)
|
||||
for risk_data in report[:10]: # Show top 10
|
||||
print(f"{risk_data['Risk ID']}: {risk_data['Risk Name']}")
|
||||
print(f" ALE: {risk_data['ALE']} | Risk Level: {risk_data['Risk Level']} | "
|
||||
f"SLA: {risk_data['Remediation SLA']}")
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File '{args.input_file}' not found")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+444
@@ -0,0 +1,444 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Vulnerability Prioritization Tool
|
||||
|
||||
Prioritizes vulnerabilities based on CVSS score combined with business context
|
||||
factors such as asset criticality, exposure, exploit availability, and compensating controls.
|
||||
|
||||
Usage:
|
||||
python vuln_prioritizer.py vulnerabilities.csv
|
||||
python vuln_prioritizer.py vulnerabilities.csv --output prioritized.csv
|
||||
python vuln_prioritizer.py --interactive
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
@dataclass
|
||||
class Vulnerability:
|
||||
"""Vulnerability data class"""
|
||||
cve_id: str
|
||||
title: str
|
||||
cvss_score: float
|
||||
affected_system: str
|
||||
asset_criticality: int # 1-5 scale
|
||||
exposure: str # internet_facing, internal, isolated
|
||||
data_sensitivity: str # highly_confidential, confidential, public
|
||||
exploit_available: bool
|
||||
exploit_in_wild: bool
|
||||
compensating_controls: bool
|
||||
discovered_date: str
|
||||
|
||||
|
||||
class VulnerabilityPrioritizer:
|
||||
"""Vulnerability prioritization engine"""
|
||||
|
||||
EXPOSURE_WEIGHT = {
|
||||
"internet_facing": 3,
|
||||
"internal": 2,
|
||||
"isolated": 1
|
||||
}
|
||||
|
||||
DATA_SENSITIVITY_WEIGHT = {
|
||||
"highly_confidential": 3, # PII, PHI, financial
|
||||
"confidential": 2,
|
||||
"public": 1
|
||||
}
|
||||
|
||||
SLA_MAPPING = {
|
||||
"P0": 1, # Critical - patch within 24-48 hours
|
||||
"P1": 7, # High - patch within 7 days
|
||||
"P2": 30, # Medium - patch within 30 days
|
||||
"P3": 90 # Low - patch within 90 days
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.vulnerabilities: List[Vulnerability] = []
|
||||
|
||||
def calculate_priority_score(self, vuln: Vulnerability) -> float:
|
||||
"""
|
||||
Calculate priority score based on CVSS + business context
|
||||
|
||||
Formula:
|
||||
Priority Score = CVSS × exploit_multiplier × asset_multiplier × exposure_multiplier × data_sensitivity_multiplier × controls_multiplier
|
||||
"""
|
||||
|
||||
# Base CVSS score (0-10)
|
||||
cvss_score = vuln.cvss_score
|
||||
|
||||
# Exploit multipliers
|
||||
exploit_available_mult = 1.5 if vuln.exploit_available else 1.0
|
||||
exploit_in_wild_mult = 2.0 if vuln.exploit_in_wild else 1.0
|
||||
|
||||
# Asset criticality multiplier (1-5 scale normalized)
|
||||
asset_mult = vuln.asset_criticality / 3.0
|
||||
|
||||
# Exposure multiplier
|
||||
exposure_mult = self.EXPOSURE_WEIGHT.get(vuln.exposure, 2) / 2.0
|
||||
|
||||
# Data sensitivity multiplier
|
||||
data_sens_mult = self.DATA_SENSITIVITY_WEIGHT.get(vuln.data_sensitivity, 2) / 2.0
|
||||
|
||||
# Compensating controls reduction
|
||||
controls_mult = 0.5 if vuln.compensating_controls else 1.0
|
||||
|
||||
# Calculate final priority score
|
||||
priority_score = (
|
||||
cvss_score *
|
||||
exploit_available_mult *
|
||||
exploit_in_wild_mult *
|
||||
asset_mult *
|
||||
exposure_mult *
|
||||
data_sens_mult *
|
||||
controls_mult
|
||||
)
|
||||
|
||||
return priority_score
|
||||
|
||||
def determine_priority_level(self, priority_score: float) -> str:
|
||||
"""Determine priority level (P0-P3) based on score"""
|
||||
if priority_score >= 14:
|
||||
return "P0" # Critical
|
||||
elif priority_score >= 10:
|
||||
return "P1" # High
|
||||
elif priority_score >= 6:
|
||||
return "P2" # Medium
|
||||
else:
|
||||
return "P3" # Low
|
||||
|
||||
def calculate_due_date(self, vuln: Vulnerability, priority_level: str) -> str:
|
||||
"""Calculate patch due date based on priority level"""
|
||||
sla_days = self.SLA_MAPPING.get(priority_level, 90)
|
||||
discovered = datetime.strptime(vuln.discovered_date, "%Y-%m-%d")
|
||||
due_date = discovered + timedelta(days=sla_days)
|
||||
return due_date.strftime("%Y-%m-%d")
|
||||
|
||||
def generate_rationale(self, vuln: Vulnerability, priority_score: float) -> str:
|
||||
"""Generate human-readable rationale for prioritization"""
|
||||
factors = []
|
||||
|
||||
if vuln.cvss_score >= 9.0:
|
||||
factors.append("Critical CVSS score")
|
||||
elif vuln.cvss_score >= 7.0:
|
||||
factors.append("High CVSS score")
|
||||
|
||||
if vuln.exploit_in_wild:
|
||||
factors.append("Active exploitation in wild")
|
||||
|
||||
if vuln.exploit_available:
|
||||
factors.append("Public exploit available")
|
||||
|
||||
if vuln.exposure == "internet_facing":
|
||||
factors.append("Internet-facing system")
|
||||
|
||||
if vuln.asset_criticality >= 4:
|
||||
factors.append("Critical business system")
|
||||
|
||||
if vuln.data_sensitivity == "highly_confidential":
|
||||
factors.append("Contains sensitive data (PII/PHI)")
|
||||
|
||||
if vuln.compensating_controls:
|
||||
factors.append("Compensating controls in place (WAF/IPS)")
|
||||
|
||||
return "; ".join(factors) if factors else "Standard risk assessment"
|
||||
|
||||
def add_vulnerability(self, vuln: Vulnerability):
|
||||
"""Add vulnerability to assessment"""
|
||||
self.vulnerabilities.append(vuln)
|
||||
|
||||
def generate_report(self) -> List[Dict]:
|
||||
"""Generate prioritized vulnerability report"""
|
||||
report = []
|
||||
|
||||
for vuln in self.vulnerabilities:
|
||||
priority_score = self.calculate_priority_score(vuln)
|
||||
priority_level = self.determine_priority_level(priority_score)
|
||||
due_date = self.calculate_due_date(vuln, priority_level)
|
||||
rationale = self.generate_rationale(vuln, priority_score)
|
||||
|
||||
report.append({
|
||||
"CVE ID": vuln.cve_id,
|
||||
"Title": vuln.title,
|
||||
"Affected System": vuln.affected_system,
|
||||
"CVSS Score": f"{vuln.cvss_score:.1f}",
|
||||
"Priority Score": f"{priority_score:.2f}",
|
||||
"Priority Level": priority_level,
|
||||
"SLA Days": self.SLA_MAPPING[priority_level],
|
||||
"Discovered": vuln.discovered_date,
|
||||
"Due Date": due_date,
|
||||
"Exploit Available": "Yes" if vuln.exploit_available else "No",
|
||||
"Active Exploitation": "Yes" if vuln.exploit_in_wild else "No",
|
||||
"Asset Criticality": vuln.asset_criticality,
|
||||
"Exposure": vuln.exposure,
|
||||
"Data Sensitivity": vuln.data_sensitivity,
|
||||
"Compensating Controls": "Yes" if vuln.compensating_controls else "No",
|
||||
"Rationale": rationale
|
||||
})
|
||||
|
||||
# Sort by priority score (descending)
|
||||
report.sort(key=lambda x: float(x["Priority Score"]), reverse=True)
|
||||
|
||||
return report
|
||||
|
||||
def generate_summary(self) -> Dict:
|
||||
"""Generate summary statistics"""
|
||||
if not self.vulnerabilities:
|
||||
return {}
|
||||
|
||||
priority_counts = {"P0": 0, "P1": 0, "P2": 0, "P3": 0}
|
||||
|
||||
for vuln in self.vulnerabilities:
|
||||
priority_score = self.calculate_priority_score(vuln)
|
||||
priority_level = self.determine_priority_level(priority_score)
|
||||
priority_counts[priority_level] += 1
|
||||
|
||||
# Count exploitable vulnerabilities
|
||||
exploitable = sum(1 for v in self.vulnerabilities if v.exploit_available)
|
||||
actively_exploited = sum(1 for v in self.vulnerabilities if v.exploit_in_wild)
|
||||
|
||||
# Count by exposure
|
||||
internet_facing = sum(1 for v in self.vulnerabilities if v.exposure == "internet_facing")
|
||||
|
||||
return {
|
||||
"total_vulnerabilities": len(self.vulnerabilities),
|
||||
"priority_distribution": priority_counts,
|
||||
"exploitable_count": exploitable,
|
||||
"actively_exploited_count": actively_exploited,
|
||||
"internet_facing_count": internet_facing
|
||||
}
|
||||
|
||||
|
||||
def load_vulnerabilities_from_csv(filename: str) -> List[Vulnerability]:
|
||||
"""Load vulnerabilities from CSV file"""
|
||||
vulnerabilities = []
|
||||
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
vuln = Vulnerability(
|
||||
cve_id=row['cve_id'],
|
||||
title=row['title'],
|
||||
cvss_score=float(row['cvss_score']),
|
||||
affected_system=row['affected_system'],
|
||||
asset_criticality=int(row['asset_criticality']),
|
||||
exposure=row['exposure'],
|
||||
data_sensitivity=row['data_sensitivity'],
|
||||
exploit_available=row['exploit_available'].lower() == 'true',
|
||||
exploit_in_wild=row['exploit_in_wild'].lower() == 'true',
|
||||
compensating_controls=row['compensating_controls'].lower() == 'true',
|
||||
discovered_date=row['discovered_date']
|
||||
)
|
||||
vulnerabilities.append(vuln)
|
||||
|
||||
return vulnerabilities
|
||||
|
||||
|
||||
def save_report_to_csv(report: List[Dict], filename: str):
|
||||
"""Save vulnerability report to CSV file"""
|
||||
if not report:
|
||||
print("No data to save")
|
||||
return
|
||||
|
||||
with open(filename, 'w', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=report[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(report)
|
||||
|
||||
print(f"✓ Report saved to {filename}")
|
||||
|
||||
|
||||
def interactive_mode():
|
||||
"""Interactive vulnerability prioritization mode"""
|
||||
prioritizer = VulnerabilityPrioritizer()
|
||||
|
||||
print("=" * 60)
|
||||
print("Vulnerability Prioritization Tool - Interactive Mode")
|
||||
print("=" * 60)
|
||||
|
||||
while True:
|
||||
print("\nOptions:")
|
||||
print("1. Add new vulnerability")
|
||||
print("2. Generate prioritization report")
|
||||
print("3. View summary statistics")
|
||||
print("4. Exit")
|
||||
|
||||
choice = input("\nEnter choice (1-4): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
print("\n--- Add New Vulnerability ---")
|
||||
cve_id = input("CVE ID (e.g., CVE-2021-44228): ").strip()
|
||||
title = input("Title: ").strip()
|
||||
cvss_score = float(input("CVSS Score (0-10): "))
|
||||
affected_system = input("Affected System: ").strip()
|
||||
asset_criticality = int(input("Asset Criticality (1-5, 5=most critical): "))
|
||||
|
||||
print("\nExposure:")
|
||||
print(" 1. internet_facing")
|
||||
print(" 2. internal")
|
||||
print(" 3. isolated")
|
||||
exposure_choice = input("Select (1-3): ")
|
||||
exposure_map = {"1": "internet_facing", "2": "internal", "3": "isolated"}
|
||||
exposure = exposure_map.get(exposure_choice, "internal")
|
||||
|
||||
print("\nData Sensitivity:")
|
||||
print(" 1. highly_confidential (PII/PHI/Financial)")
|
||||
print(" 2. confidential")
|
||||
print(" 3. public")
|
||||
sens_choice = input("Select (1-3): ")
|
||||
sens_map = {"1": "highly_confidential", "2": "confidential", "3": "public"}
|
||||
data_sensitivity = sens_map.get(sens_choice, "confidential")
|
||||
|
||||
exploit_available = input("Public exploit available? (yes/no): ").lower() == "yes"
|
||||
exploit_in_wild = input("Active exploitation in wild? (yes/no): ").lower() == "yes"
|
||||
compensating_controls = input("Compensating controls in place? (yes/no): ").lower() == "yes"
|
||||
discovered_date = input("Discovered date (YYYY-MM-DD): ").strip()
|
||||
|
||||
vuln = Vulnerability(
|
||||
cve_id, title, cvss_score, affected_system, asset_criticality,
|
||||
exposure, data_sensitivity, exploit_available, exploit_in_wild,
|
||||
compensating_controls, discovered_date
|
||||
)
|
||||
prioritizer.add_vulnerability(vuln)
|
||||
|
||||
# Calculate and display priority
|
||||
priority_score = prioritizer.calculate_priority_score(vuln)
|
||||
priority_level = prioritizer.determine_priority_level(priority_score)
|
||||
due_date = prioritizer.calculate_due_date(vuln, priority_level)
|
||||
rationale = prioritizer.generate_rationale(vuln, priority_score)
|
||||
|
||||
print(f"\n✓ Vulnerability added successfully!")
|
||||
print(f" Priority Score: {priority_score:.2f}")
|
||||
print(f" Priority Level: {priority_level}")
|
||||
print(f" SLA: Patch within {prioritizer.SLA_MAPPING[priority_level]} days")
|
||||
print(f" Due Date: {due_date}")
|
||||
print(f" Rationale: {rationale}")
|
||||
|
||||
elif choice == "2":
|
||||
if not prioritizer.vulnerabilities:
|
||||
print("No vulnerabilities added yet. Please add a vulnerability first.")
|
||||
continue
|
||||
|
||||
report = prioritizer.generate_report()
|
||||
|
||||
print("\n" + "=" * 150)
|
||||
print("Vulnerability Prioritization Report")
|
||||
print("=" * 150)
|
||||
print(f"{'CVE ID':<20} {'System':<25} {'CVSS':<6} {'Priority':<10} {'Level':<7} {'Due Date':<12} {'Rationale':<50}")
|
||||
print("-" * 150)
|
||||
|
||||
for row in report:
|
||||
print(f"{row['CVE ID']:<20} "
|
||||
f"{row['Affected System']:<25} "
|
||||
f"{row['CVSS Score']:<6} "
|
||||
f"{row['Priority Score']:<10} "
|
||||
f"{row['Priority Level']:<7} "
|
||||
f"{row['Due Date']:<12} "
|
||||
f"{row['Rationale']:<50}")
|
||||
|
||||
elif choice == "3":
|
||||
summary = prioritizer.generate_summary()
|
||||
if not summary:
|
||||
print("No vulnerabilities added yet. Please add a vulnerability first.")
|
||||
continue
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Vulnerability Summary")
|
||||
print("=" * 60)
|
||||
print(f"Total Vulnerabilities: {summary['total_vulnerabilities']}")
|
||||
print(f"\nPriority Distribution:")
|
||||
for level, count in summary['priority_distribution'].items():
|
||||
print(f" {level}: {count}")
|
||||
print(f"\nExploitability:")
|
||||
print(f" Public exploits available: {summary['exploitable_count']}")
|
||||
print(f" Active exploitation: {summary['actively_exploited_count']}")
|
||||
print(f"\nExposure:")
|
||||
print(f" Internet-facing systems: {summary['internet_facing_count']}")
|
||||
|
||||
elif choice == "4":
|
||||
print("Exiting...")
|
||||
break
|
||||
|
||||
else:
|
||||
print("Invalid choice. Please enter 1-4.")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Vulnerability Prioritization Tool")
|
||||
parser.add_argument('input_file', nargs='?', help='CSV file containing vulnerability data')
|
||||
parser.add_argument('--output', '-o', help='Output CSV file for prioritized report')
|
||||
parser.add_argument('--interactive', '-i', action='store_true',
|
||||
help='Run in interactive mode')
|
||||
parser.add_argument('--filter-level', choices=['P0', 'P1', 'P2', 'P3'],
|
||||
help='Filter to show only specified priority level')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.interactive:
|
||||
interactive_mode()
|
||||
return
|
||||
|
||||
if not args.input_file:
|
||||
print("Error: Please provide an input file or use --interactive mode")
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
try:
|
||||
vulnerabilities = load_vulnerabilities_from_csv(args.input_file)
|
||||
prioritizer = VulnerabilityPrioritizer()
|
||||
|
||||
for vuln in vulnerabilities:
|
||||
prioritizer.add_vulnerability(vuln)
|
||||
|
||||
print(f"✓ Loaded {len(vulnerabilities)} vulnerabilities from {args.input_file}")
|
||||
|
||||
# Generate report
|
||||
report = prioritizer.generate_report()
|
||||
|
||||
# Filter if requested
|
||||
if args.filter_level:
|
||||
report = [r for r in report if r['Priority Level'] == args.filter_level]
|
||||
|
||||
# Display summary
|
||||
summary = prioritizer.generate_summary()
|
||||
print("\n" + "=" * 60)
|
||||
print("Vulnerability Summary")
|
||||
print("=" * 60)
|
||||
print(f"Total Vulnerabilities: {summary['total_vulnerabilities']}")
|
||||
print(f"\nPriority Distribution:")
|
||||
for level, count in summary['priority_distribution'].items():
|
||||
print(f" {level}: {count}")
|
||||
print(f"\nExploitability:")
|
||||
print(f" Public exploits available: {summary['exploitable_count']}")
|
||||
print(f" Active exploitation: {summary['actively_exploited_count']}")
|
||||
|
||||
# Display top prioritized vulnerabilities
|
||||
print("\n" + "=" * 150)
|
||||
print("Top Prioritized Vulnerabilities")
|
||||
print("=" * 150)
|
||||
print(f"{'CVE ID':<20} {'System':<30} {'CVSS':<6} {'Priority':<10} {'Level':<7} {'Due Date':<12}")
|
||||
print("-" * 150)
|
||||
|
||||
for row in report[:15]: # Show top 15
|
||||
print(f"{row['CVE ID']:<20} "
|
||||
f"{row['Affected System']:<30} "
|
||||
f"{row['CVSS Score']:<6} "
|
||||
f"{row['Priority Score']:<10} "
|
||||
f"{row['Priority Level']:<7} "
|
||||
f"{row['Due Date']:<12}")
|
||||
|
||||
# Save report if output file specified
|
||||
if args.output:
|
||||
save_report_to_csv(report, args.output)
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File '{args.input_file}' not found")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user