From 56827470ac23f19f4b839cc2b1cfe2b3c57eb661 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 7 Apr 2026 13:11:59 -0400 Subject: [PATCH] Adding script to analyze risk objects on TTP detections --- scripts/analyze_ttp_risk_objects.py | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 scripts/analyze_ttp_risk_objects.py diff --git a/scripts/analyze_ttp_risk_objects.py b/scripts/analyze_ttp_risk_objects.py new file mode 100644 index 0000000000..3fed5fb47a --- /dev/null +++ b/scripts/analyze_ttp_risk_objects.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +""" +Analyze TTP detections with multiple risk_objects. + +Finds all YAML files under detections/**/*.yml where: + - type == TTP + - rba.risk_objects has 2 or more entries + +Aggregates results by the set of risk_object types and reports +files that do NOT follow the standard (1 user + 1 system) pattern. + +Usage: + uv run --with pyyaml scripts/analyze_ttp_risk_objects.py +""" + +import os +import yaml +from collections import defaultdict + +DETECTIONS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "detections") + + +def main(): + results = defaultdict(list) + total_ttp_with_multi_risk = 0 + + for root, dirs, files in os.walk(DETECTIONS_DIR): + for fname in files: + if not fname.endswith(".yml"): + continue + fpath = os.path.join(root, fname) + try: + with open(fpath) as f: + data = yaml.safe_load(f) + except Exception: + continue + + if not isinstance(data, dict): + continue + if data.get("type") != "TTP": + continue + + rba = data.get("rba") + if not isinstance(rba, dict): + continue + + risk_objects = rba.get("risk_objects", []) + if not isinstance(risk_objects, list) or len(risk_objects) < 2: + continue + + total_ttp_with_multi_risk += 1 + types = tuple(sorted(ro.get("type", "unknown") for ro in risk_objects)) + results[types].append(fpath) + + print(f"Total TTP files with 2+ risk_objects: {total_ttp_with_multi_risk}\n") + print("Aggregated by risk_object_type set:") + for type_combo, files in sorted(results.items(), key=lambda x: -len(x[1])): + print(f" {list(type_combo)} — {len(files)} file(s)") + + print("\nNon-(1 user + 1 system) combos:") + non_standard_total = 0 + for type_combo, files in sorted(results.items(), key=lambda x: -len(x[1])): + if type_combo == ("system", "user"): + continue + non_standard_total += len(files) + print(f" {list(type_combo)} — {len(files)} file(s)") + for f in files: + print(f" {os.path.relpath(f, DETECTIONS_DIR)}") + + print(f"\nTotal non-(1 user + 1 system): {non_standard_total}") + + +if __name__ == "__main__": + main()