mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-06 15:58:56 +03:00
243 lines
8.2 KiB
Python
243 lines
8.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
CIS Benchmark Compliance Processor
|
|
|
|
Parses CIS-CAT Pro XML assessment results, calculates compliance scores,
|
|
identifies failed controls, and generates remediation reports.
|
|
"""
|
|
|
|
import xml.etree.ElementTree as ET
|
|
import json
|
|
import csv
|
|
import sys
|
|
import os
|
|
from datetime import datetime
|
|
from collections import defaultdict
|
|
|
|
|
|
def parse_ciscat_xccdf_results(xml_path: str) -> dict:
|
|
"""Parse CIS-CAT XCCDF results XML into structured findings."""
|
|
tree = ET.parse(xml_path)
|
|
root = tree.getroot()
|
|
|
|
ns = {
|
|
"xccdf": "http://checklists.nist.gov/xccdf/1.2",
|
|
"cpe": "http://cpe.mitre.org/dictionary/2.0",
|
|
}
|
|
|
|
results = {
|
|
"benchmark_id": "",
|
|
"benchmark_title": "",
|
|
"profile": "",
|
|
"target": "",
|
|
"scan_date": "",
|
|
"total_rules": 0,
|
|
"passed": 0,
|
|
"failed": 0,
|
|
"not_applicable": 0,
|
|
"error": 0,
|
|
"score": 0.0,
|
|
"findings": [],
|
|
}
|
|
|
|
benchmark = root.find(".//xccdf:benchmark", ns)
|
|
if benchmark is not None:
|
|
results["benchmark_id"] = benchmark.get("id", "")
|
|
|
|
title = root.find(".//xccdf:title", ns)
|
|
if title is not None:
|
|
results["benchmark_title"] = title.text or ""
|
|
|
|
test_result = root.find(".//xccdf:TestResult", ns)
|
|
if test_result is not None:
|
|
results["scan_date"] = test_result.get("end-time", "")
|
|
target = test_result.find("xccdf:target", ns)
|
|
if target is not None:
|
|
results["target"] = target.text or ""
|
|
|
|
profile = test_result.find("xccdf:profile", ns)
|
|
if profile is not None:
|
|
results["profile"] = profile.get("idref", "")
|
|
|
|
for rule_result in test_result.findall("xccdf:rule-result", ns):
|
|
rule_id = rule_result.get("idref", "")
|
|
result_elem = rule_result.find("xccdf:result", ns)
|
|
result_value = result_elem.text if result_elem is not None else "unknown"
|
|
|
|
results["total_rules"] += 1
|
|
|
|
if result_value == "pass":
|
|
results["passed"] += 1
|
|
elif result_value == "fail":
|
|
results["failed"] += 1
|
|
results["findings"].append(
|
|
{
|
|
"rule_id": rule_id,
|
|
"result": "FAIL",
|
|
"severity": rule_result.get("severity", "unknown"),
|
|
}
|
|
)
|
|
elif result_value == "notapplicable":
|
|
results["not_applicable"] += 1
|
|
else:
|
|
results["error"] += 1
|
|
|
|
scored_rules = results["passed"] + results["failed"]
|
|
if scored_rules > 0:
|
|
results["score"] = round((results["passed"] / scored_rules) * 100, 2)
|
|
|
|
return results
|
|
|
|
|
|
def generate_remediation_csv(findings: list, output_path: str) -> None:
|
|
"""Generate CSV of failed controls for remediation tracking."""
|
|
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
|
writer = csv.writer(f)
|
|
writer.writerow(
|
|
[
|
|
"Rule ID",
|
|
"Severity",
|
|
"Result",
|
|
"Remediation Status",
|
|
"Assigned To",
|
|
"Due Date",
|
|
"Notes",
|
|
]
|
|
)
|
|
for finding in findings:
|
|
writer.writerow(
|
|
[
|
|
finding["rule_id"],
|
|
finding["severity"],
|
|
finding["result"],
|
|
"Open",
|
|
"",
|
|
"",
|
|
"",
|
|
]
|
|
)
|
|
|
|
|
|
def compare_scan_results(current: dict, previous: dict) -> dict:
|
|
"""Compare two CIS-CAT scan results to detect compliance drift."""
|
|
drift = {
|
|
"current_score": current["score"],
|
|
"previous_score": previous["score"],
|
|
"score_delta": round(current["score"] - previous["score"], 2),
|
|
"new_failures": [],
|
|
"resolved_failures": [],
|
|
"persistent_failures": [],
|
|
}
|
|
|
|
current_failed = {f["rule_id"] for f in current["findings"]}
|
|
previous_failed = {f["rule_id"] for f in previous["findings"]}
|
|
|
|
drift["new_failures"] = sorted(current_failed - previous_failed)
|
|
drift["resolved_failures"] = sorted(previous_failed - current_failed)
|
|
drift["persistent_failures"] = sorted(current_failed & previous_failed)
|
|
|
|
return drift
|
|
|
|
|
|
def generate_compliance_report(results: dict, output_path: str) -> None:
|
|
"""Generate a compliance summary report in JSON format."""
|
|
report = {
|
|
"report_generated": datetime.utcnow().isoformat() + "Z",
|
|
"benchmark": results["benchmark_title"],
|
|
"profile": results["profile"],
|
|
"target_host": results["target"],
|
|
"scan_date": results["scan_date"],
|
|
"compliance_score": results["score"],
|
|
"summary": {
|
|
"total_rules_assessed": results["total_rules"],
|
|
"passed": results["passed"],
|
|
"failed": results["failed"],
|
|
"not_applicable": results["not_applicable"],
|
|
"errors": results["error"],
|
|
},
|
|
"failed_controls": results["findings"],
|
|
"compliance_status": "COMPLIANT"
|
|
if results["score"] >= 95.0
|
|
else "NON-COMPLIANT",
|
|
}
|
|
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
json.dump(report, f, indent=2)
|
|
|
|
|
|
def categorize_findings_by_section(findings: list) -> dict:
|
|
"""Group failed findings by CIS benchmark section for prioritized remediation."""
|
|
sections = defaultdict(list)
|
|
|
|
section_map = {
|
|
"1.1": "Password Policy",
|
|
"1.2": "Account Lockout Policy",
|
|
"2.2": "User Rights Assignment",
|
|
"2.3": "Security Options",
|
|
"5": "System Services",
|
|
"9": "Windows Firewall",
|
|
"17": "Advanced Audit Policy",
|
|
"18": "Administrative Templates",
|
|
"19": "Administrative Templates (User)",
|
|
}
|
|
|
|
for finding in findings:
|
|
rule_id = finding["rule_id"]
|
|
categorized = False
|
|
for prefix, section_name in section_map.items():
|
|
if f"_section_{prefix}" in rule_id.lower() or f".{prefix}." in rule_id:
|
|
sections[section_name].append(finding)
|
|
categorized = True
|
|
break
|
|
if not categorized:
|
|
sections["Other"].append(finding)
|
|
|
|
return dict(sections)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python process.py <ciscat-results.xml> [previous-results.xml]")
|
|
print()
|
|
print("Parses CIS-CAT XCCDF results and generates compliance reports.")
|
|
print()
|
|
print("Arguments:")
|
|
print(" ciscat-results.xml Current CIS-CAT assessment results")
|
|
print(" previous-results.xml (Optional) Previous results for drift detection")
|
|
sys.exit(1)
|
|
|
|
xml_path = sys.argv[1]
|
|
if not os.path.exists(xml_path):
|
|
print(f"Error: File not found: {xml_path}")
|
|
sys.exit(1)
|
|
|
|
results = parse_ciscat_xccdf_results(xml_path)
|
|
base_name = os.path.splitext(os.path.basename(xml_path))[0]
|
|
output_dir = os.path.dirname(xml_path) or "."
|
|
|
|
report_path = os.path.join(output_dir, f"{base_name}_compliance_report.json")
|
|
generate_compliance_report(results, report_path)
|
|
print(f"Compliance report: {report_path}")
|
|
|
|
if results["findings"]:
|
|
csv_path = os.path.join(output_dir, f"{base_name}_remediation.csv")
|
|
generate_remediation_csv(results["findings"], csv_path)
|
|
print(f"Remediation tracker: {csv_path}")
|
|
|
|
print(f"\nCompliance Score: {results['score']}%")
|
|
print(f"Status: {'COMPLIANT' if results['score'] >= 95.0 else 'NON-COMPLIANT'}")
|
|
print(f"Passed: {results['passed']} | Failed: {results['failed']} | N/A: {results['not_applicable']}")
|
|
|
|
if len(sys.argv) >= 3:
|
|
prev_path = sys.argv[2]
|
|
if os.path.exists(prev_path):
|
|
prev_results = parse_ciscat_xccdf_results(prev_path)
|
|
drift = compare_scan_results(results, prev_results)
|
|
drift_path = os.path.join(output_dir, f"{base_name}_drift.json")
|
|
with open(drift_path, "w") as f:
|
|
json.dump(drift, f, indent=2)
|
|
print(f"\nDrift report: {drift_path}")
|
|
print(f"Score delta: {drift['score_delta']:+.2f}%")
|
|
print(f"New failures: {len(drift['new_failures'])}")
|
|
print(f"Resolved: {len(drift['resolved_failures'])}")
|