mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-14 03:15:16 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
176 lines
6.6 KiB
Python
176 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Alert Triage with Elastic SIEM agent - queries Elasticsearch SIEM signals
|
|
index to retrieve, prioritize, and triage security alerts using
|
|
elasticsearch-py client."""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from collections import Counter
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from elasticsearch import Elasticsearch
|
|
except ImportError:
|
|
print("Install elasticsearch: pip install elasticsearch", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
SIEM_SIGNALS_INDEX = ".siem-signals-*"
|
|
|
|
SEVERITY_PRIORITY = {"critical": 1, "high": 2, "medium": 3, "low": 4}
|
|
|
|
|
|
def create_client(host: str, api_key: str = None, username: str = None,
|
|
password: str = None, verify_certs: bool = True) -> Elasticsearch:
|
|
"""Create Elasticsearch client."""
|
|
kwargs = {"hosts": [host], "verify_certs": verify_certs}
|
|
if api_key:
|
|
kwargs["api_key"] = api_key
|
|
elif username and password:
|
|
kwargs["basic_auth"] = (username, password)
|
|
return Elasticsearch(**kwargs)
|
|
|
|
|
|
def get_open_alerts(es: Elasticsearch, hours_back: int = 24,
|
|
severity: list[str] = None, size: int = 500) -> list[dict]:
|
|
"""Retrieve open SIEM alerts from the signals index."""
|
|
must_clauses = [
|
|
{"range": {"@timestamp": {"gte": f"now-{hours_back}h", "lte": "now"}}},
|
|
{"term": {"signal.status": "open"}},
|
|
]
|
|
if severity:
|
|
must_clauses.append({"terms": {"signal.rule.severity": severity}})
|
|
|
|
query = {"bool": {"must": must_clauses}}
|
|
response = es.search(index=SIEM_SIGNALS_INDEX, query=query, size=size,
|
|
sort=[{"@timestamp": {"order": "desc"}}])
|
|
alerts = []
|
|
for hit in response["hits"]["hits"]:
|
|
src = hit["_source"]
|
|
signal = src.get("signal", {})
|
|
rule = signal.get("rule", {})
|
|
alerts.append({
|
|
"alert_id": hit["_id"],
|
|
"timestamp": src.get("@timestamp", ""),
|
|
"rule_name": rule.get("name", ""),
|
|
"rule_id": rule.get("id", ""),
|
|
"severity": rule.get("severity", "unknown"),
|
|
"risk_score": rule.get("risk_score", 0),
|
|
"status": signal.get("status", ""),
|
|
"source_ip": src.get("source", {}).get("ip", ""),
|
|
"destination_ip": src.get("destination", {}).get("ip", ""),
|
|
"user": src.get("user", {}).get("name", ""),
|
|
"host": src.get("host", {}).get("name", ""),
|
|
"process": src.get("process", {}).get("name", ""),
|
|
})
|
|
return alerts
|
|
|
|
|
|
def get_alert_aggregations(es: Elasticsearch, hours_back: int = 24) -> dict:
|
|
"""Aggregate alerts by rule, severity, and host."""
|
|
query = {
|
|
"bool": {
|
|
"must": [
|
|
{"range": {"@timestamp": {"gte": f"now-{hours_back}h"}}},
|
|
{"term": {"signal.status": "open"}},
|
|
]
|
|
}
|
|
}
|
|
aggs = {
|
|
"by_severity": {"terms": {"field": "signal.rule.severity", "size": 10}},
|
|
"by_rule": {"terms": {"field": "signal.rule.name.keyword", "size": 20}},
|
|
"by_host": {"terms": {"field": "host.name.keyword", "size": 20}},
|
|
"by_user": {"terms": {"field": "user.name.keyword", "size": 20}},
|
|
}
|
|
response = es.search(index=SIEM_SIGNALS_INDEX, query=query, aggs=aggs, size=0)
|
|
result = {}
|
|
for agg_name, agg_data in response.get("aggregations", {}).items():
|
|
result[agg_name] = [
|
|
{"key": bucket["key"], "count": bucket["doc_count"]}
|
|
for bucket in agg_data.get("buckets", [])
|
|
]
|
|
return result
|
|
|
|
|
|
def prioritize_alerts(alerts: list[dict]) -> list[dict]:
|
|
"""Sort and prioritize alerts by severity and risk score."""
|
|
return sorted(alerts, key=lambda a: (
|
|
SEVERITY_PRIORITY.get(a.get("severity", "low"), 5),
|
|
-a.get("risk_score", 0)
|
|
))
|
|
|
|
|
|
def identify_alert_clusters(alerts: list[dict]) -> list[dict]:
|
|
"""Group related alerts that may represent a single incident."""
|
|
clusters = []
|
|
by_host = {}
|
|
for alert in alerts:
|
|
host = alert.get("host", "unknown")
|
|
if host not in by_host:
|
|
by_host[host] = []
|
|
by_host[host].append(alert)
|
|
|
|
for host, host_alerts in by_host.items():
|
|
if len(host_alerts) >= 3:
|
|
rules = list(set(a["rule_name"] for a in host_alerts))
|
|
max_severity = min(host_alerts, key=lambda a: SEVERITY_PRIORITY.get(a.get("severity", "low"), 5))
|
|
clusters.append({
|
|
"host": host,
|
|
"alert_count": len(host_alerts),
|
|
"unique_rules": len(rules),
|
|
"rules": rules[:10],
|
|
"max_severity": max_severity["severity"],
|
|
"recommendation": "Investigate as potential incident - multiple alerts on same host",
|
|
})
|
|
return clusters
|
|
|
|
|
|
def generate_report(host: str, api_key: str = None, username: str = None,
|
|
password: str = None, hours_back: int = 24,
|
|
severity: list[str] = None) -> dict:
|
|
"""Run alert triage and build consolidated report."""
|
|
es = create_client(host, api_key, username, password)
|
|
alerts = get_open_alerts(es, hours_back, severity)
|
|
prioritized = prioritize_alerts(alerts)
|
|
aggregations = get_alert_aggregations(es, hours_back)
|
|
clusters = identify_alert_clusters(alerts)
|
|
|
|
severity_counts = Counter(a["severity"] for a in alerts)
|
|
return {
|
|
"report": "elastic_siem_alert_triage",
|
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
"time_window_hours": hours_back,
|
|
"total_open_alerts": len(alerts),
|
|
"severity_summary": dict(severity_counts),
|
|
"alert_clusters": clusters,
|
|
"aggregations": aggregations,
|
|
"prioritized_alerts": prioritized[:50],
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Elastic SIEM Alert Triage Agent")
|
|
parser.add_argument("--host", required=True, help="Elasticsearch URL")
|
|
parser.add_argument("--api-key", help="Elasticsearch API key")
|
|
parser.add_argument("--username", help="Elasticsearch username")
|
|
parser.add_argument("--password", help="Elasticsearch password")
|
|
parser.add_argument("--hours", type=int, default=24, help="Hours to look back (default: 24)")
|
|
parser.add_argument("--severity", nargs="+", help="Filter by severity levels")
|
|
parser.add_argument("--output", help="Output JSON file path")
|
|
args = parser.parse_args()
|
|
|
|
report = generate_report(args.host, args.api_key, args.username,
|
|
args.password, args.hours, args.severity)
|
|
output = json.dumps(report, indent=2)
|
|
if args.output:
|
|
Path(args.output).write_text(output, encoding="utf-8")
|
|
print(f"Report written to {args.output}")
|
|
else:
|
|
print(output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|