Files

141 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""
BAS Results Analyzer and Control Effectiveness Calculator
Processes Breach and Attack Simulation results to calculate
security control effectiveness scores and identify gaps.
Requirements:
pip install pandas
Usage:
python process.py analyze --csv bas_results.csv --output control_scores.csv
python process.py gaps --csv bas_results.csv --output gaps.csv
python process.py trend --dir results/ --output trend.csv
"""
import argparse
import sys
from collections import defaultdict
from datetime import datetime
from pathlib import Path
import pandas as pd
CONTROL_TECHNIQUE_MAP = {
"email_gateway": ["T1566.001", "T1566.002", "T1566.003"],
"edr": ["T1059.001", "T1059.003", "T1003.001", "T1055", "T1547.001",
"T1053.005", "T1027", "T1140"],
"ngfw_proxy": ["T1071.001", "T1071.004", "T1048.001", "T1048.003",
"T1572", "T1090"],
"siem": ["T1087", "T1018", "T1069", "T1021.002", "T1021.001"],
"dlp": ["T1048", "T1567", "T1041", "T1560"],
"ndr": ["T1071", "T1021", "T1040", "T1046"],
"waf": ["T1190", "T1210"],
"pam": ["T1078", "T1134", "T1098"],
}
def analyze_control_effectiveness(df):
"""Calculate control effectiveness from BAS results."""
scores = []
for control, techniques in CONTROL_TECHNIQUE_MAP.items():
relevant = df[df["technique_id"].isin(techniques)]
if len(relevant) == 0:
continue
total = len(relevant)
prevented = len(relevant[relevant["result"] == "prevented"])
detected = len(relevant[relevant["result"] == "detected"])
missed = len(relevant[relevant["result"] == "missed"])
scores.append({
"control": control,
"total_tests": total,
"prevented": prevented,
"detected": detected,
"missed": missed,
"prevention_rate": round(prevented / total * 100, 1),
"detection_rate": round(detected / total * 100, 1),
"effectiveness": round((prevented + detected) / total * 100, 1),
"gap_rate": round(missed / total * 100, 1),
})
return pd.DataFrame(scores).sort_values("effectiveness", ascending=True)
def identify_gaps(df):
"""Identify attack techniques that bypass all controls."""
missed = df[df["result"] == "missed"].copy()
if len(missed) == 0:
print("[+] No gaps found - all attacks were prevented or detected!")
return pd.DataFrame()
gaps = missed.groupby(["technique_id", "technique_name"]).agg(
miss_count=("result", "count"),
targets=("target", lambda x: ", ".join(x.unique())),
).reset_index().sort_values("miss_count", ascending=False)
return gaps
def print_summary(scores_df, gaps_df):
"""Print analysis summary."""
print(f"\n{'=' * 70}")
print("BAS CONTROL EFFECTIVENESS REPORT")
print(f"{'=' * 70}")
print(f"\nControl Scores:")
for _, row in scores_df.iterrows():
status = "PASS" if row["effectiveness"] >= 80 else "WARN" if row["effectiveness"] >= 60 else "FAIL"
print(f" [{status}] {row['control']:<15} "
f"Effectiveness: {row['effectiveness']}% "
f"(Prevent: {row['prevention_rate']}% | "
f"Detect: {row['detection_rate']}% | "
f"Miss: {row['gap_rate']}%)")
if len(gaps_df) > 0:
print(f"\nTop Security Gaps (attacks that bypass controls):")
for _, row in gaps_df.head(10).iterrows():
print(f" {row['technique_id']}: {row['technique_name']} "
f"({row['miss_count']} misses)")
def main():
parser = argparse.ArgumentParser(description="BAS Results Analyzer")
subparsers = parser.add_subparsers(dest="command")
a_p = subparsers.add_parser("analyze", help="Analyze control effectiveness")
a_p.add_argument("--csv", required=True)
a_p.add_argument("--output", default="control_scores.csv")
g_p = subparsers.add_parser("gaps", help="Identify security gaps")
g_p.add_argument("--csv", required=True)
g_p.add_argument("--output", default="gaps.csv")
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
df = pd.read_csv(args.csv)
if args.command == "analyze":
scores = analyze_control_effectiveness(df)
gaps = identify_gaps(df)
print_summary(scores, gaps)
scores.to_csv(args.output, index=False)
print(f"\n[+] Control scores saved to {args.output}")
elif args.command == "gaps":
gaps = identify_gaps(df)
gaps.to_csv(args.output, index=False)
print(f"[+] {len(gaps)} gaps saved to {args.output}")
if __name__ == "__main__":
main()