npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
Overview
Alert triage in Elastic Security is the systematic process of reviewing, classifying, and prioritizing security alerts to determine which represent genuine threats. Elastic's AI-driven Attack Discovery feature can triage hundreds of alerts down to discrete attack chains, but skilled analyst triage remains essential. A structured triage workflow typically takes 5-10 minutes per alert cluster using Elastic's built-in tools.
When to Use
- When conducting security assessments that involve performing alert triage with elastic siem
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- Elastic Security deployed (version 8.x or later)
- Elastic Agent or Beats configured for endpoint and network data collection
- Detection rules enabled and generating alerts
- Elastic Common Schema (ECS) compliance across data sources
- Analyst access to Kibana Security app with appropriate privileges
Alert Triage Workflow
Step 1: Initial Alert Assessment (2 minutes)
When viewing an alert in Elastic Security, review the alert details panel:
Alert Details Panel:
- Rule Name and Description
- Severity and Risk Score
- MITRE ATT&CK Mapping
- Host and User Context
- Process Tree (for endpoint alerts)
- Timeline of related eventsKey Fields to Examine First
| Field | Purpose | ECS Field |
|---|---|---|
| Rule severity | Initial priority assessment | kibana.alert.severity |
| Risk score | Quantified threat level | kibana.alert.risk_score |
| Host name | Affected system | host.name |
| User name | Affected identity | user.name |
| Process name | Executing process | process.name |
| Source IP | Origin of activity | source.ip |
| Destination IP | Target of activity | destination.ip |
| MITRE tactic | Attack stage | threat.tactic.name |
Step 2: Context Gathering (3 minutes)
Query Related Events with ES|QL
FROM logs-endpoint.events.*
| WHERE host.name == "affected-host" AND @timestamp > NOW() - 1 HOUR
| STATS count = COUNT(*) BY event.category, event.action
| SORT count DESCFind All Activity from Suspicious User
FROM logs-*
| WHERE user.name == "suspicious-user" AND @timestamp > NOW() - 24 HOURS
| STATS count = COUNT(*), unique_hosts = COUNT_DISTINCT(host.name) BY event.category
| SORT count DESCCheck for Related Alerts from Same Source
FROM .alerts-security.alerts-default
| WHERE source.ip == "10.0.0.50" AND @timestamp > NOW() - 24 HOURS
| STATS alert_count = COUNT(*) BY kibana.alert.rule.name, kibana.alert.severity
| SORT alert_count DESCInvestigate Lateral Movement from Same IP
FROM logs-system.auth-*
| WHERE source.ip == "10.0.0.50" AND event.outcome == "success"
| STATS login_count = COUNT(*), hosts = COUNT_DISTINCT(host.name) BY user.name
| WHERE hosts > 3Step 3: Threat Intelligence Enrichment (2 minutes)
Check indicators against threat intelligence:
FROM logs-ti_*
| WHERE threat.indicator.ip == "203.0.113.50"
| KEEP threat.indicator.type, threat.indicator.provider, threat.indicator.confidence, threat.feed.nameCheck File Hash Against Known Threats
FROM logs-endpoint.events.file-*
| WHERE file.hash.sha256 == "abc123..."
| STATS occurrences = COUNT(*) BY host.name, file.path, user.nameStep 4: Classification Decision (2 minutes)
| Classification | Criteria | Action |
|---|---|---|
| True Positive | Confirmed malicious activity | Escalate to incident, begin containment |
| Benign True Positive | Expected behavior matching rule | Document in alert notes, acknowledge |
| False Positive | Rule triggered on benign activity | Mark as false positive, create tuning task |
| Needs Investigation | Insufficient data for determination | Assign for deeper investigation |
Step 5: Documentation and Escalation (1 minute)
For each triaged alert, document:
- Classification decision with rationale
- Evidence artifacts examined
- Related alerts or investigations
- Recommended next steps
Detection Rules for Triage
Pre-Built Detection Rules
Elastic Security includes 1000+ pre-built detection rules organized by:
- MITRE ATT&CK Tactic: Initial Access, Execution, Persistence, etc.
- Platform: Windows, Linux, macOS, Cloud
- Data Source: Endpoint, Network, Cloud, Identity
Custom Alert Correlation Rule
{
"name": "Multiple Failed Logins Followed by Success",
"type": "threshold",
"query": "event.category:authentication AND event.outcome:failure",
"threshold": {
"field": ["source.ip", "user.name"],
"value": 5,
"cardinality": [
{
"field": "user.name",
"value": 3
}
]
},
"severity": "high",
"risk_score": 73,
"threat": [
{
"framework": "MITRE ATT&CK",
"tactic": {
"id": "TA0006",
"name": "Credential Access"
},
"technique": [
{
"id": "T1110",
"name": "Brute Force"
}
]
}
]
}AI-Assisted Triage
Elastic AI Assistant Integration
- Open alert in Elastic Security
- Click AI Assistant panel
- Use quick prompts:
- "Summarize this alert" - Get initial assessment
- "Generate ES|QL query to find related activity" - Expand investigation
- "What are the recommended response actions?" - Get playbook guidance
- "Is this likely a false positive?" - Get AI confidence assessment
Attack Discovery
Elastic's Attack Discovery automatically:
- Groups related alerts into attack chains
- Maps alerts to MITRE ATT&CK kill chain stages
- Filters false positives using ML models
- Prioritizes based on business impact
- Provides narrative summary of the attack
Triage Prioritization Matrix
| Risk Score | Severity | Asset Criticality | Response SLA |
|---|---|---|---|
| 90-100 | Critical | High | 15 minutes |
| 70-89 | High | High | 30 minutes |
| 70-89 | High | Medium | 1 hour |
| 50-69 | Medium | Any | 4 hours |
| 21-49 | Low | Any | 8 hours |
| 1-20 | Informational | Any | 24 hours |
Triage Metrics and KPIs
| Metric | Target | Measurement |
|---|---|---|
| Mean Time to Triage (MTTT) | < 10 minutes | Time from alert creation to classification |
| False Positive Rate | < 30% | False positives / total alerts |
| Escalation Rate | 10-20% | Escalated alerts / total alerts |
| Alert Coverage | > 80% | Triaged alerts / generated alerts per shift |
| Reclassification Rate | < 5% | Changed classifications / total classified |
References
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md2.8 KB
Alert Triage with Elastic SIEM - API Reference
elasticsearch-py Client
Connection
from elasticsearch import Elasticsearch
es = Elasticsearch(
hosts=["https://elastic:9200"],
api_key="base64-api-key",
verify_certs=True
)SIEM Signals Index
Elastic Security stores alerts in .siem-signals-<space>-* indices.
Querying Alerts
Search Open Alerts
es.search(
index=".siem-signals-*",
query={"bool": {"must": [
{"range": {"@timestamp": {"gte": "now-24h"}}},
{"term": {"signal.status": "open"}}
]}},
sort=[{"@timestamp": {"order": "desc"}}],
size=500
)Alert Fields
| Field | Path | Description |
|---|---|---|
| Rule name | signal.rule.name |
Detection rule that triggered |
| Rule ID | signal.rule.id |
Unique rule identifier |
| Severity | signal.rule.severity |
critical, high, medium, low |
| Risk score | signal.rule.risk_score |
0-100 numeric score |
| Status | signal.status |
open, acknowledged, closed |
| Source IP | source.ip |
Alert source address |
| Destination IP | destination.ip |
Alert destination address |
| User | user.name |
Associated username |
| Host | host.name |
Affected hostname |
| Process | process.name |
Triggering process |
Aggregations
es.search(
index=".siem-signals-*",
query={"bool": {"must": [...]}},
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}}
},
size=0
)Alert Status Management
Update Alert Status
es.update(
index=".siem-signals-default-000001",
id="alert_doc_id",
body={"doc": {"signal": {"status": "closed"}}}
)Triage Prioritization
Severity Priority
- Critical (risk score 90-100)
- High (risk score 70-89)
- Medium (risk score 40-69)
- Low (risk score 0-39)
Alert Clustering
Alerts from the same host within a time window are grouped as potential incidents. Three or more alerts from the same host suggest a multi-stage attack.
Elastic Security API
List Detection Rules
GET /api/detection_engine/rules/_find?per_page=100Get Rule Execution Status
GET /api/detection_engine/rules/_find_statusesOutput Schema
{
"report": "elastic_siem_alert_triage",
"total_open_alerts": 45,
"severity_summary": {"critical": 3, "high": 12, "medium": 20, "low": 10},
"alert_clusters": [{"host": "web01", "alert_count": 5, "max_severity": "high"}],
"aggregations": {"by_severity": [{"key": "high", "count": 12}]}
}CLI Usage
python agent.py --host https://elastic:9200 --api-key "key" --hours 24 --output report.jsonstandards.md1.7 KB
Standards and References - Alert Triage with Elastic SIEM
Elastic Common Schema (ECS)
ECS is a standardized field naming convention for Elasticsearch data. All Elastic Security detections and triage workflows rely on ECS compliance.
Key ECS Field Categories for Triage
| Category | Fields | Usage |
|---|---|---|
| Base | @timestamp, message, tags |
Event timing and classification |
| Agent | agent.name, agent.type |
Data source identification |
| Host | host.name, host.ip, host.os |
Affected system context |
| User | user.name, user.domain |
Identity attribution |
| Process | process.name, process.pid, process.command_line |
Execution context |
| Network | source.ip, destination.ip, destination.port |
Network activity |
| File | file.name, file.hash.sha256, file.path |
File-related events |
| Threat | threat.tactic.name, threat.technique.id |
MITRE ATT&CK mapping |
MITRE ATT&CK Integration
Elastic Security maps detection rules and alerts to MITRE ATT&CK tactics and techniques, providing a common taxonomy for triage prioritization.
NIST SP 800-61 Rev 2
Triage aligns with NIST incident handling phases:
- Detection and Analysis (triage is the core of this phase)
- Prioritization based on functional impact, information impact, and recoverability
SOC Maturity Model
Triage Capability Levels
| Level | Capability |
|---|---|
| Level 1 | Manual review of individual alerts |
| Level 2 | Grouped alert triage with correlation |
| Level 3 | AI-assisted triage with automated enrichment |
| Level 4 | Automated classification with human oversight |
| Level 5 | Fully autonomous triage with exception-based review |
workflows.md1.6 KB
Workflows - Alert Triage with Elastic SIEM
5-Step Rapid Triage Framework
1. Alert Reception (30 seconds)
- Review alert title, severity, risk score
- Check MITRE ATT&CK mapping
|
v
2. Context Assessment (2 minutes)
- Examine affected host and user
- Check asset criticality
- Review process tree for endpoint alerts
|
v
3. Intelligence Enrichment (2 minutes)
- Check threat intelligence feeds
- Query for related alerts (same source/user)
- Search for known IOCs
|
v
4. Classification (1 minute)
- True Positive / False Positive / Needs Investigation
- Assign confidence level
|
v
5. Action (2 minutes)
- Document findings in alert notes
- Escalate or close with rationale
- Create tuning task if false positiveAlert Grouping Strategy
Smart Grouping Criteria
- Time window: Group alerts within 15-minute windows
- Entity: Group by affected host or user
- Kill chain stage: Group by MITRE ATT&CK tactic
- Source: Group by originating IP or detection rule
Group Triage Process
- Sort alert groups by highest severity member
- Triage group as single unit when correlated
- Escalate entire group if attack chain detected
- Close group if false positive pattern identified
Shift-Based Triage Queue Management
| Queue Priority | Alert Criteria | Analyst Tier |
|---|---|---|
| Immediate | Critical severity, critical assets | Tier 2+ |
| High | High severity or multiple related alerts | Tier 1/2 |
| Standard | Medium severity, standard assets | Tier 1 |
| Low | Low/info severity, non-critical | Tier 1 (batch review) |
Scripts 2
agent.py6.6 KB
#!/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()
process.py11.2 KB
#!/usr/bin/env python3
"""
Elastic SIEM Alert Triage Automation
Provides alert triage scoring, classification assistance,
and triage workflow management for Elastic Security alerts.
"""
import json
from datetime import datetime, timedelta
from typing import Optional
SEVERITY_WEIGHTS = {
"critical": 100,
"high": 75,
"medium": 50,
"low": 25,
"informational": 10,
}
ASSET_CRITICALITY = {
"critical": 1.5,
"high": 1.3,
"medium": 1.0,
"low": 0.7,
}
MITRE_TACTIC_PRIORITY = {
"Impact": 95,
"Exfiltration": 90,
"Lateral Movement": 85,
"Credential Access": 80,
"Command and Control": 75,
"Defense Evasion": 70,
"Privilege Escalation": 65,
"Persistence": 60,
"Execution": 55,
"Collection": 50,
"Discovery": 40,
"Initial Access": 35,
"Reconnaissance": 20,
"Resource Development": 15,
}
class TriageAlert:
"""Represents a security alert for triage processing."""
def __init__(
self,
alert_id: str,
rule_name: str,
severity: str,
risk_score: int,
host_name: str,
user_name: str,
source_ip: str,
mitre_tactic: str,
mitre_technique: str,
timestamp: str,
asset_criticality: str = "medium",
related_alert_count: int = 0,
threat_intel_match: bool = False,
):
self.alert_id = alert_id
self.rule_name = rule_name
self.severity = severity
self.risk_score = risk_score
self.host_name = host_name
self.user_name = user_name
self.source_ip = source_ip
self.mitre_tactic = mitre_tactic
self.mitre_technique = mitre_technique
self.timestamp = timestamp
self.asset_criticality = asset_criticality
self.related_alert_count = related_alert_count
self.threat_intel_match = threat_intel_match
self.classification = None
self.triage_notes = []
self.triage_timestamp = None
def calculate_triage_priority(self) -> dict:
"""Calculate composite triage priority score."""
base_score = SEVERITY_WEIGHTS.get(self.severity, 50)
asset_multiplier = ASSET_CRITICALITY.get(self.asset_criticality, 1.0)
tactic_score = MITRE_TACTIC_PRIORITY.get(self.mitre_tactic, 50)
# Boost for related alerts (potential attack chain)
chain_boost = min(self.related_alert_count * 5, 25)
# Boost for threat intelligence match
ti_boost = 20 if self.threat_intel_match else 0
composite_score = min(100, (base_score * asset_multiplier * 0.4)
+ (tactic_score * 0.3)
+ chain_boost
+ ti_boost)
if composite_score >= 85:
priority = "P1-CRITICAL"
sla = "15 minutes"
elif composite_score >= 70:
priority = "P2-HIGH"
sla = "30 minutes"
elif composite_score >= 50:
priority = "P3-MEDIUM"
sla = "4 hours"
elif composite_score >= 30:
priority = "P4-LOW"
sla = "8 hours"
else:
priority = "P5-INFO"
sla = "24 hours"
return {
"composite_score": round(composite_score, 1),
"priority": priority,
"response_sla": sla,
"score_breakdown": {
"severity_component": round(base_score * asset_multiplier * 0.4, 1),
"tactic_component": round(tactic_score * 0.3, 1),
"chain_boost": chain_boost,
"threat_intel_boost": ti_boost,
},
}
def classify(self, classification: str, notes: str):
"""Classify the alert after triage."""
valid_classifications = [
"true_positive",
"false_positive",
"benign_true_positive",
"needs_investigation",
]
if classification not in valid_classifications:
raise ValueError(f"Invalid classification. Must be one of: {valid_classifications}")
self.classification = classification
self.triage_notes.append(notes)
self.triage_timestamp = datetime.utcnow().isoformat()
def to_dict(self) -> dict:
priority = self.calculate_triage_priority()
return {
"alert_id": self.alert_id,
"rule_name": self.rule_name,
"severity": self.severity,
"risk_score": self.risk_score,
"host_name": self.host_name,
"user_name": self.user_name,
"source_ip": self.source_ip,
"mitre_tactic": self.mitre_tactic,
"mitre_technique": self.mitre_technique,
"timestamp": self.timestamp,
"triage_priority": priority,
"classification": self.classification,
"triage_notes": self.triage_notes,
"triage_timestamp": self.triage_timestamp,
}
class TriageQueue:
"""Manages a queue of alerts for SOC triage."""
def __init__(self):
self.alerts = []
self.triaged = []
self.metrics = {
"total_received": 0,
"total_triaged": 0,
"true_positives": 0,
"false_positives": 0,
"benign_true_positives": 0,
"pending_investigation": 0,
"triage_times": [],
}
def add_alert(self, alert: TriageAlert):
self.alerts.append(alert)
self.metrics["total_received"] += 1
def get_prioritized_queue(self) -> list:
"""Return alerts sorted by triage priority score (highest first)."""
return sorted(
self.alerts,
key=lambda a: a.calculate_triage_priority()["composite_score"],
reverse=True,
)
def triage_alert(self, alert_id: str, classification: str, notes: str, triage_duration_seconds: int):
for i, alert in enumerate(self.alerts):
if alert.alert_id == alert_id:
alert.classify(classification, notes)
self.triaged.append(alert)
self.alerts.pop(i)
self.metrics["total_triaged"] += 1
self.metrics["triage_times"].append(triage_duration_seconds)
if classification == "true_positive":
self.metrics["true_positives"] += 1
elif classification == "false_positive":
self.metrics["false_positives"] += 1
elif classification == "benign_true_positive":
self.metrics["benign_true_positives"] += 1
elif classification == "needs_investigation":
self.metrics["pending_investigation"] += 1
return alert
return None
def get_triage_metrics(self) -> dict:
times = self.metrics["triage_times"]
total = self.metrics["total_triaged"]
return {
"total_received": self.metrics["total_received"],
"total_triaged": total,
"pending": len(self.alerts),
"true_positive_count": self.metrics["true_positives"],
"false_positive_count": self.metrics["false_positives"],
"false_positive_rate": round(self.metrics["false_positives"] / total * 100, 1) if total > 0 else 0,
"mean_triage_time_seconds": round(sum(times) / len(times), 1) if times else 0,
"max_triage_time_seconds": max(times) if times else 0,
"min_triage_time_seconds": min(times) if times else 0,
}
def generate_esql_queries(alert: TriageAlert) -> dict:
"""Generate ES|QL queries for investigating an alert."""
return {
"related_host_activity": (
f'FROM logs-*\n'
f'| WHERE host.name == "{alert.host_name}" '
f'AND @timestamp > NOW() - 1 HOUR\n'
f'| STATS count = COUNT(*) BY event.category, event.action\n'
f'| SORT count DESC'
),
"user_activity": (
f'FROM logs-*\n'
f'| WHERE user.name == "{alert.user_name}" '
f'AND @timestamp > NOW() - 24 HOURS\n'
f'| STATS count = COUNT(*), '
f'unique_hosts = COUNT_DISTINCT(host.name) BY event.category\n'
f'| SORT count DESC'
),
"source_ip_alerts": (
f'FROM .alerts-security.alerts-default\n'
f'| WHERE source.ip == "{alert.source_ip}" '
f'AND @timestamp > NOW() - 24 HOURS\n'
f'| STATS alert_count = COUNT(*) '
f'BY kibana.alert.rule.name, kibana.alert.severity\n'
f'| SORT alert_count DESC'
),
"network_connections": (
f'FROM logs-endpoint.events.network-*\n'
f'| WHERE host.name == "{alert.host_name}" '
f'AND @timestamp > NOW() - 1 HOUR\n'
f'| STATS conn_count = COUNT(*) BY destination.ip, destination.port\n'
f'| SORT conn_count DESC\n'
f'| LIMIT 20'
),
}
if __name__ == "__main__":
queue = TriageQueue()
sample_alerts = [
TriageAlert("ALT-001", "Multiple Failed Logins Followed by Success", "high", 73,
"DC01", "admin", "10.0.0.50", "Credential Access", "T1110",
"2025-01-15T10:30:00Z", "critical", 3, False),
TriageAlert("ALT-002", "Suspicious PowerShell Execution", "critical", 91,
"WS-042", "jsmith", "10.0.1.42", "Execution", "T1059.001",
"2025-01-15T10:32:00Z", "high", 5, True),
TriageAlert("ALT-003", "Unusual DNS Query Volume", "medium", 47,
"WS-108", "mwilson", "10.0.2.108", "Command and Control", "T1071",
"2025-01-15T10:35:00Z", "medium", 0, False),
TriageAlert("ALT-004", "New Windows Service Created", "low", 21,
"SRV-DB01", "svc-deploy", "10.0.3.10", "Persistence", "T1543.003",
"2025-01-15T10:37:00Z", "low", 0, False),
]
for alert in sample_alerts:
queue.add_alert(alert)
print("=" * 70)
print("ELASTIC SIEM ALERT TRIAGE QUEUE")
print("=" * 70)
prioritized = queue.get_prioritized_queue()
for i, alert in enumerate(prioritized, 1):
p = alert.calculate_triage_priority()
print(f"\n{i}. [{p['priority']}] {alert.rule_name}")
print(f" Score: {p['composite_score']} | SLA: {p['response_sla']}")
print(f" Host: {alert.host_name} | User: {alert.user_name} | Tactic: {alert.mitre_tactic}")
# Simulate triage
queue.triage_alert("ALT-002", "true_positive", "Confirmed Mimikatz execution via encoded PS", 180)
queue.triage_alert("ALT-001", "true_positive", "Brute force followed by successful admin login", 300)
queue.triage_alert("ALT-003", "false_positive", "DNS queries to known CDN - normal behavior", 120)
queue.triage_alert("ALT-004", "benign_true_positive", "SCCM deployment creating expected service", 90)
print(f"\n{'=' * 70}")
print("TRIAGE METRICS")
print("=" * 70)
metrics = queue.get_triage_metrics()
for key, value in metrics.items():
print(f" {key}: {value}")
# Show investigation queries
print(f"\n{'=' * 70}")
print("ES|QL INVESTIGATION QUERIES FOR ALT-002")
print("=" * 70)
queries = generate_esql_queries(sample_alerts[1])
for name, query in queries.items():
print(f"\n--- {name} ---")
print(query)