mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
196 lines
7.0 KiB
Python
196 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Patch Tuesday Response Automation Tool
|
|
|
|
Fetches Microsoft Patch Tuesday advisory data, cross-references with
|
|
CISA KEV, and generates a prioritized deployment plan.
|
|
|
|
Requirements:
|
|
pip install requests pandas
|
|
|
|
Usage:
|
|
python process.py fetch --month 2025-12 # Fetch patches for month
|
|
python process.py analyze --month 2025-12 # Analyze and prioritize
|
|
python process.py plan --month 2025-12 --output deployment_plan.csv
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
import pandas as pd
|
|
import requests
|
|
|
|
|
|
MSRC_API = "https://api.msrc.microsoft.com/cvrf/v3.0/cvrf"
|
|
MSRC_UPDATES_API = "https://api.msrc.microsoft.com/cvrf/v3.0/updates"
|
|
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
|
|
EPSS_API = "https://api.first.org/data/v1/epss"
|
|
|
|
|
|
class PatchTuesdayAnalyzer:
|
|
"""Analyze and prioritize Patch Tuesday security updates."""
|
|
|
|
SEVERITY_MAP = {
|
|
"Critical": 4,
|
|
"Important": 3,
|
|
"Moderate": 2,
|
|
"Low": 1,
|
|
}
|
|
|
|
RING_ASSIGNMENT = {
|
|
"zero_day": {"ring": 0, "name": "Emergency", "sla_hours": 48},
|
|
"critical_rce": {"ring": 0, "name": "Emergency", "sla_hours": 72},
|
|
"critical": {"ring": 1, "name": "Pilot", "sla_hours": 168},
|
|
"important": {"ring": 2, "name": "Production", "sla_hours": 336},
|
|
"moderate": {"ring": 3, "name": "Workstations", "sla_hours": 504},
|
|
"low": {"ring": 4, "name": "Maintenance", "sla_hours": 720},
|
|
}
|
|
|
|
def __init__(self):
|
|
self.session = requests.Session()
|
|
self.session.headers.update({
|
|
"Accept": "application/json",
|
|
"User-Agent": "PatchTuesday-Analyzer/1.0"
|
|
})
|
|
self.kev_catalog = set()
|
|
|
|
def load_kev_catalog(self):
|
|
"""Load CISA KEV catalog for cross-reference."""
|
|
try:
|
|
response = self.session.get(KEV_URL, timeout=30)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
self.kev_catalog = {
|
|
v["cveID"] for v in data.get("vulnerabilities", [])
|
|
}
|
|
print(f"[+] Loaded {len(self.kev_catalog)} CVEs from CISA KEV")
|
|
except Exception as e:
|
|
print(f"[!] Failed to load KEV: {e}")
|
|
|
|
def get_epss_scores(self, cve_list):
|
|
"""Fetch EPSS scores for CVEs."""
|
|
scores = {}
|
|
for i in range(0, len(cve_list), 100):
|
|
batch = cve_list[i:i + 100]
|
|
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"]] = float(entry.get("epss", 0))
|
|
except Exception as e:
|
|
print(f" [!] EPSS error: {e}")
|
|
return scores
|
|
|
|
def classify_patch(self, cve_data):
|
|
"""Classify a patch into deployment ring."""
|
|
is_exploited = cve_data.get("actively_exploited", False)
|
|
in_kev = cve_data.get("cve_id", "") in self.kev_catalog
|
|
severity = cve_data.get("severity", "").lower()
|
|
attack_type = cve_data.get("attack_type", "").lower()
|
|
cvss = float(cve_data.get("cvss_score", 0))
|
|
epss = float(cve_data.get("epss_score", 0))
|
|
|
|
if is_exploited or in_kev:
|
|
return self.RING_ASSIGNMENT["zero_day"]
|
|
elif severity == "critical" and "remote code execution" in attack_type:
|
|
return self.RING_ASSIGNMENT["critical_rce"]
|
|
elif severity == "critical" or cvss >= 9.0 or epss > 0.7:
|
|
return self.RING_ASSIGNMENT["critical"]
|
|
elif severity == "important" or cvss >= 7.0:
|
|
return self.RING_ASSIGNMENT["important"]
|
|
elif severity == "moderate" or cvss >= 4.0:
|
|
return self.RING_ASSIGNMENT["moderate"]
|
|
else:
|
|
return self.RING_ASSIGNMENT["low"]
|
|
|
|
def generate_deployment_plan(self, patches):
|
|
"""Generate a ring-based deployment plan."""
|
|
self.load_kev_catalog()
|
|
|
|
cve_list = [p["cve_id"] for p in patches if p.get("cve_id")]
|
|
epss_scores = self.get_epss_scores(cve_list)
|
|
|
|
plan = []
|
|
for patch in patches:
|
|
cve_id = patch.get("cve_id", "")
|
|
patch["epss_score"] = epss_scores.get(cve_id, 0)
|
|
patch["in_cisa_kev"] = cve_id in self.kev_catalog
|
|
|
|
ring = self.classify_patch(patch)
|
|
|
|
plan.append({
|
|
"cve_id": cve_id,
|
|
"product": patch.get("product", ""),
|
|
"severity": patch.get("severity", ""),
|
|
"attack_type": patch.get("attack_type", ""),
|
|
"cvss_score": patch.get("cvss_score", 0),
|
|
"epss_score": round(patch.get("epss_score", 0), 4),
|
|
"in_cisa_kev": patch.get("in_cisa_kev", False),
|
|
"actively_exploited": patch.get("actively_exploited", False),
|
|
"deployment_ring": ring["ring"],
|
|
"ring_name": ring["name"],
|
|
"sla_hours": ring["sla_hours"],
|
|
"kb_article": patch.get("kb_article", ""),
|
|
})
|
|
|
|
df = pd.DataFrame(plan)
|
|
df = df.sort_values(["deployment_ring", "cvss_score"],
|
|
ascending=[True, False])
|
|
return df
|
|
|
|
def print_summary(self, df):
|
|
"""Print deployment plan summary."""
|
|
print(f"\n{'=' * 70}")
|
|
print("PATCH TUESDAY DEPLOYMENT PLAN")
|
|
print(f"{'=' * 70}")
|
|
print(f"Total patches: {len(df)}")
|
|
print(f"Zero-day/KEV (Ring 0): {len(df[df['deployment_ring'] == 0])}")
|
|
|
|
print(f"\nBy Severity:")
|
|
print(df["severity"].value_counts().to_string())
|
|
|
|
print(f"\nBy Deployment Ring:")
|
|
for ring in sorted(df["deployment_ring"].unique()):
|
|
ring_data = df[df["deployment_ring"] == ring]
|
|
ring_name = ring_data.iloc[0]["ring_name"]
|
|
sla = ring_data.iloc[0]["sla_hours"]
|
|
print(f" Ring {ring} ({ring_name}): {len(ring_data)} patches, "
|
|
f"SLA: {sla}h")
|
|
|
|
print(f"\nBy Attack Type:")
|
|
print(df["attack_type"].value_counts().head(5).to_string())
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Patch Tuesday Response Automation"
|
|
)
|
|
subparsers = parser.add_subparsers(dest="command")
|
|
|
|
plan_p = subparsers.add_parser("plan", help="Generate deployment plan from CSV")
|
|
plan_p.add_argument("--csv", required=True, help="Input CSV of patches")
|
|
plan_p.add_argument("--output", default="deployment_plan.csv")
|
|
|
|
args = parser.parse_args()
|
|
analyzer = PatchTuesdayAnalyzer()
|
|
|
|
if args.command == "plan":
|
|
df = pd.read_csv(args.csv)
|
|
patches = df.to_dict("records")
|
|
plan = analyzer.generate_deployment_plan(patches)
|
|
analyzer.print_summary(plan)
|
|
plan.to_csv(args.output, index=False)
|
|
print(f"\n[+] Deployment plan saved to {args.output}")
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|