incident response

Triaging Security Incidents

Performs initial triage of security incidents to determine severity, scope, and required response actions using the NIST SP 800-61r3 and SANS PICERL frameworks. Classifies incidents by type, assigns priority based on business impact, and routes to appropriate response teams. Activates for requests involving incident triage, security alert classification, severity assessment, incident prioritization, or initial incident analysis.

incident-triagenist-800-61sans-picerlseverity-classificationsoc-operations
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • A SIEM or EDR alert fires and requires human classification before escalation
  • Multiple concurrent alerts arrive and the SOC must prioritize response order
  • An end user reports suspicious activity and the incident needs initial categorization
  • A threat intelligence feed matches an IOC observed in the environment

Do not use for routine vulnerability scanning results or compliance audit findings that do not represent active security incidents.

Prerequisites

  • Access to SIEM platform (Splunk, Elastic, Microsoft Sentinel) with current alert data
  • Incident classification taxonomy aligned to NIST SP 800-61r3 categories
  • Predefined severity matrix mapping asset criticality to threat type
  • Contact roster for escalation paths (Tier 1 through Tier 3 and CIRT)
  • Asset inventory with business criticality ratings

Workflow

Step 1: Collect Initial Alert Data

Gather all available context from the triggering alert before making classification decisions:

  • Alert source: Which detection system generated the alert (EDR, SIEM, IDS/IPS, firewall, user report)
  • Timestamp: When the event occurred and when it was detected (dwell time gap)
  • Affected assets: Hostnames, IP addresses, user accounts involved
  • Alert fidelity: Historical true-positive rate for this detection rule
  • Raw evidence: Log entries, packet captures, process execution chains
Example SIEM alert context:
Source:       CrowdStrike Falcon
Detection:    Suspicious PowerShell Execution (T1059.001)
Host:         WORKSTATION-FIN-042
User:         jsmith@corp.example.com
Timestamp:    2025-11-15T14:23:17Z
Severity:     High (detection rule confidence: 92%)
Process:      powershell.exe -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoA...
Parent:       outlook.exe (PID 4812)

Step 2: Classify the Incident Type

Map the alert to a standard incident category per NIST SP 800-61r3:

Category Examples
Unauthorized Access Compromised credentials, privilege escalation, IDOR
Denial of Service Volumetric DDoS, application-layer flood, resource exhaustion
Malicious Code Malware execution, ransomware detonation, cryptominer
Improper Usage Policy violation, insider data exfiltration, shadow IT
Reconnaissance Port scanning, directory enumeration, credential spraying
Web Application Attack SQL injection, XSS, SSRF exploitation

Step 3: Assign Severity Using Impact Matrix

Calculate severity by combining asset criticality with threat severity:

Severity = f(Asset Criticality, Threat Type, Data Sensitivity, Lateral Movement Potential)
 
Critical (P1): Crown jewel systems compromised, active data exfiltration, ransomware spreading
High (P2):     Production system compromise, confirmed malware execution, privileged account takeover
Medium (P3):   Non-production compromise, unsuccessful exploitation attempt, single endpoint malware
Low (P4):      Reconnaissance activity, policy violation, benign true positive

Response SLA targets:

  • P1: Acknowledge within 15 minutes, containment within 1 hour
  • P2: Acknowledge within 30 minutes, containment within 4 hours
  • P3: Acknowledge within 2 hours, investigation within 24 hours
  • P4: Acknowledge within 8 hours, investigation within 72 hours

Step 4: Perform Initial Enrichment

Before escalation, enrich the alert with contextual data:

  • Threat intelligence: Check IOCs (IP, hash, domain) against TI platforms (VirusTotal, OTX, MISP)
  • Asset context: Query CMDB for asset owner, business function, data classification
  • User context: Check identity provider for recent authentication anomalies, MFA status
  • Historical correlation: Search for related alerts on the same host/user in the past 30 days
  • Network context: Verify if source/destination IPs are internal, known partners, or external threat actors

Step 5: Document and Escalate

Create a structured triage record and route to the appropriate response tier:

Incident Triage Record
━━━━━━━━━━━━━━━━━━━━━
Ticket ID:       INC-2025-1547
Triage Analyst:  [analyst name]
Triage Time:     2025-11-15T14:35:00Z (12 min from alert)
Classification:  Malicious Code - Macro-based initial access
Severity:        P2 - High
Affected Assets: WORKSTATION-FIN-042 (Finance dept, handles PII)
Affected Users:  jsmith@corp.example.com
IOCs Identified: powershell.exe spawned by outlook.exe, encoded command
TI Matches:      Base64 payload matches known Qakbot loader pattern
Escalation:      Tier 2 - Malware IR team
Recommended:     Isolate endpoint, preserve memory dump, block sender domain

Step 6: Initiate Containment Hold

If severity is P1 or P2, initiate immediate containment actions while awaiting full investigation:

  • Network-isolate the affected endpoint via EDR (CrowdStrike contain, Defender isolate)
  • Disable compromised user accounts in Active Directory or identity provider
  • Block identified malicious IPs/domains at firewall and DNS sinkhole
  • Preserve volatile evidence (memory dump) before any remediation

Key Concepts

Term Definition
Triage Rapid assessment process to classify and prioritize security incidents based on severity and business impact
PICERL SANS incident response framework: Preparation, Identification, Containment, Eradication, Recovery, Lessons Learned
Dwell Time Duration between initial compromise and detection; average is 10 days per Mandiant M-Trends 2025
True Positive Rate Percentage of alerts from a detection rule that represent genuine security incidents
Crown Jewel Assets Systems and data critical to business operations whose compromise would cause severe organizational impact
Alert Fatigue Degraded analyst performance caused by high volumes of low-fidelity or false-positive alerts
Mean Time to Acknowledge (MTTA) Average time from alert generation to analyst acknowledgment; key SOC performance metric

Tools & Systems

  • Splunk Enterprise Security: SIEM platform for alert aggregation, correlation, and triage workflow management
  • CrowdStrike Falcon: EDR platform providing endpoint telemetry, detection, and one-click host containment
  • TheHive: Open-source incident response platform for case management, task tracking, and team collaboration
  • MISP: Threat intelligence sharing platform for IOC enrichment during triage
  • Cortex XSOAR: SOAR platform for automating enrichment playbooks and triage decision trees

Common Scenarios

Scenario: Encoded PowerShell from Email Client

Context: SOC analyst receives a P2 alert showing powershell.exe with a Base64-encoded command spawned as a child process of outlook.exe on a finance department workstation.

Approach:

  1. Decode the Base64 payload to determine the command intent
  2. Check the parent process chain for anomalies (Outlook spawning PowerShell is abnormal)
  3. Query VirusTotal for the decoded payload hash
  4. Correlate with email gateway logs to identify the triggering email and sender
  5. Check if other recipients in the organization received the same email
  6. Isolate the endpoint and escalate to Tier 2 with full triage context

Pitfalls:

  • Dismissing encoded PowerShell as a false positive without decoding the payload
  • Failing to check for lateral spread to other recipients of the same phishing email
  • Remediating the endpoint before capturing volatile memory evidence

Output Format

INCIDENT TRIAGE REPORT
======================
Ticket:          INC-[YYYY]-[NNNN]
Date/Time:       [ISO 8601 timestamp]
Triage Analyst:  [Name]
Time to Triage:  [minutes from alert to classification]
 
CLASSIFICATION
Type:            [NIST category]
Severity:        [P1-P4] - [Critical/High/Medium/Low]
Confidence:      [High/Medium/Low]
MITRE ATT&CK:   [Technique ID and name]
 
AFFECTED SCOPE
Assets:          [hostname(s), IP(s)]
Users:           [account(s)]
Data at Risk:    [classification level]
Business Unit:   [department]
 
EVIDENCE SUMMARY
[Bullet list of key observations]
 
ENRICHMENT RESULTS
TI Matches:      [Yes/No - details]
Historical:      [Related prior incidents]
Asset Criticality: [rating]
 
RECOMMENDED ACTIONS
1. [Immediate action]
2. [Investigation step]
3. [Escalation target]
 
ESCALATION
Routed To:       [Team/Individual]
SLA Target:      [Containment deadline]
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.3 KB

API Reference: Triaging Security Incidents

requests Library (Threat Intel APIs)

VirusTotal API v3

headers = {"x-apikey": "<API_KEY>"}
# IP lookup
requests.get(f"https://www.virustotal.com/api/v3/ip_addresses/{ip}", headers=headers)
# File hash lookup
requests.get(f"https://www.virustotal.com/api/v3/files/{sha256}", headers=headers)
# Domain lookup
requests.get(f"https://www.virustotal.com/api/v3/domains/{domain}", headers=headers)

Response Fields

Field Description
last_analysis_stats.malicious Vendors detecting as malicious
last_analysis_stats.undetected Vendors with no detection
meaningful_name File name (for hash lookups)
reputation Community reputation score

NIST SP 800-61r3 Incident Categories

Category Examples
Unauthorized Access Credential compromise, privilege escalation
Denial of Service DDoS, resource exhaustion
Malicious Code Malware, ransomware, cryptominer
Improper Usage Policy violation, insider threat
Reconnaissance Port scan, directory enumeration
Web Application Attack SQLi, XSS, SSRF

Severity Matrix

Priority Label ACK SLA Containment SLA
P1 Critical 15 min 1 hour
P2 High 30 min 4 hours
P3 Medium 2 hours 24 hours
P4 Low 8 hours 72 hours

SANS PICERL Framework

  1. Preparation - Tools, playbooks, team readiness
  2. Identification - Detection and triage (this skill)
  3. Containment - Isolate affected systems
  4. Eradication - Remove threat from environment
  5. Recovery - Restore systems to normal operation
  6. Lessons Learned - Post-incident review

MITRE ATT&CK Mapping

Technique ID Common Alert
Brute Force T1110 Multiple failed logins
PowerShell T1059.001 Encoded PS execution
Valid Accounts T1078 Anomalous authentication
Phishing T1566 Malicious email attachment
Exploit Public App T1190 Web attack detected

References

Scripts 1

agent.py10.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for triaging security incidents using NIST SP 800-61 and SANS PICERL frameworks."""

import requests
import json
import argparse
from datetime import datetime, timezone


NIST_CATEGORIES = {
    "unauthorized_access": "Unauthorized Access",
    "dos": "Denial of Service",
    "malicious_code": "Malicious Code",
    "improper_usage": "Improper Usage",
    "reconnaissance": "Reconnaissance",
    "web_attack": "Web Application Attack",
}

SEVERITY_MATRIX = {
    "P1": {"label": "Critical", "ack_sla": "15 min", "contain_sla": "1 hour",
            "criteria": "Crown jewel compromise, active exfiltration, ransomware spreading"},
    "P2": {"label": "High", "ack_sla": "30 min", "contain_sla": "4 hours",
            "criteria": "Production compromise, confirmed malware, privileged account takeover"},
    "P3": {"label": "Medium", "ack_sla": "2 hours", "contain_sla": "24 hours",
            "criteria": "Non-production compromise, failed exploitation, single endpoint malware"},
    "P4": {"label": "Low", "ack_sla": "8 hours", "contain_sla": "72 hours",
            "criteria": "Reconnaissance, policy violation, benign true positive"},
}


def classify_incident(alert_data):
    """Classify incident type based on alert indicators."""
    print("[*] Classifying incident type...")
    alert_name = alert_data.get("alert_name", "").lower()
    process = alert_data.get("process", "").lower()
    event_code = alert_data.get("event_code", "")

    if any(kw in alert_name for kw in ["malware", "ransomware", "trojan", "cryptominer"]):
        category = "malicious_code"
    elif any(kw in alert_name for kw in ["brute force", "credential", "password spray"]):
        category = "unauthorized_access"
    elif any(kw in alert_name for kw in ["dos", "flood", "resource exhaustion"]):
        category = "dos"
    elif any(kw in alert_name for kw in ["sql injection", "xss", "ssrf", "xxe"]):
        category = "web_attack"
    elif any(kw in alert_name for kw in ["scan", "enum", "recon", "discovery"]):
        category = "reconnaissance"
    elif "powershell" in process and "encoded" in alert_name.lower():
        category = "malicious_code"
    else:
        category = "unauthorized_access"

    classification = NIST_CATEGORIES.get(category, "Unknown")
    print(f"  [+] Classification: {classification} ({category})")
    return category, classification


def assess_severity(alert_data, asset_criticality="medium"):
    """Calculate incident severity based on threat and asset context."""
    print("\n[*] Assessing severity...")
    threat_score = 0

    alert_severity = alert_data.get("severity", "").lower()
    if alert_severity in ("critical", "high"):
        threat_score += 3
    elif alert_severity == "medium":
        threat_score += 2
    else:
        threat_score += 1

    confidence = alert_data.get("confidence", 50)
    if confidence >= 80:
        threat_score += 2
    elif confidence >= 50:
        threat_score += 1

    asset_scores = {"critical": 3, "high": 2, "medium": 1, "low": 0}
    asset_score = asset_scores.get(asset_criticality, 1)

    total = threat_score + asset_score
    if total >= 7:
        priority = "P1"
    elif total >= 5:
        priority = "P2"
    elif total >= 3:
        priority = "P3"
    else:
        priority = "P4"

    sev_info = SEVERITY_MATRIX[priority]
    print(f"  [+] Priority: {priority} - {sev_info['label']}")
    print(f"  [+] ACK SLA: {sev_info['ack_sla']} | Containment SLA: {sev_info['contain_sla']}")
    return priority, sev_info


def check_virustotal(api_key, indicator, indicator_type="ip"):
    """Check an indicator against VirusTotal."""
    print(f"\n[*] Checking VirusTotal for {indicator_type}: {indicator}...")
    base_urls = {
        "ip": f"https://www.virustotal.com/api/v3/ip_addresses/{indicator}",
        "hash": f"https://www.virustotal.com/api/v3/files/{indicator}",
        "domain": f"https://www.virustotal.com/api/v3/domains/{indicator}",
    }
    url = base_urls.get(indicator_type)
    if not url:
        return {}
    try:
        headers = {"x-apikey": api_key}
        resp = requests.get(url, headers=headers, timeout=15)
        if resp.status_code == 200:
            data = resp.json().get("data", {}).get("attributes", {})
            if indicator_type in ("ip", "domain"):
                malicious = data.get("last_analysis_stats", {}).get("malicious", 0)
                total = sum(data.get("last_analysis_stats", {}).values())
                print(f"  [+] VT Result: {malicious}/{total} vendors flagged as malicious")
                return {"malicious": malicious, "total": total}
            elif indicator_type == "hash":
                malicious = data.get("last_analysis_stats", {}).get("malicious", 0)
                name = data.get("meaningful_name", "Unknown")
                print(f"  [+] VT Result: {name} - {malicious} detections")
                return {"name": name, "malicious": malicious}
        elif resp.status_code == 404:
            print(f"  [-] Not found in VirusTotal")
        else:
            print(f"  [-] VT API error: {resp.status_code}")
    except requests.RequestException as e:
        print(f"  [-] VT request failed: {e}")
    return {}


def build_mitre_mapping(category, process_info=""):
    """Map incident to MITRE ATT&CK techniques."""
    mappings = {
        "malicious_code": [
            {"technique": "T1059.001", "name": "PowerShell"},
            {"technique": "T1204.002", "name": "User Execution: Malicious File"},
        ],
        "unauthorized_access": [
            {"technique": "T1110", "name": "Brute Force"},
            {"technique": "T1078", "name": "Valid Accounts"},
        ],
        "reconnaissance": [
            {"technique": "T1046", "name": "Network Service Discovery"},
            {"technique": "T1595", "name": "Active Scanning"},
        ],
        "web_attack": [
            {"technique": "T1190", "name": "Exploit Public-Facing Application"},
        ],
    }
    techniques = mappings.get(category, [])
    if techniques:
        print(f"\n[*] MITRE ATT&CK mapping:")
        for t in techniques:
            print(f"  - {t['technique']}: {t['name']}")
    return techniques


def generate_triage_record(alert_data, classification, priority, sev_info,
                            ti_results, mitre, output_path):
    """Generate a structured incident triage report."""
    triage_time = datetime.now(timezone.utc)
    alert_time = alert_data.get("timestamp", triage_time.isoformat())

    record = {
        "ticket_id": f"INC-{triage_time.strftime('%Y')}-{hash(str(alert_data)) % 10000:04d}",
        "triage_analyst": alert_data.get("analyst", "automated"),
        "triage_time": triage_time.isoformat(),
        "alert_time": alert_time,
        "classification": {
            "type": classification[1],
            "category": classification[0],
            "priority": priority,
            "severity_label": sev_info["label"],
            "confidence": alert_data.get("confidence", "Unknown"),
        },
        "affected_scope": {
            "assets": alert_data.get("affected_hosts", []),
            "users": alert_data.get("affected_users", []),
            "business_unit": alert_data.get("business_unit", "Unknown"),
        },
        "evidence": {
            "alert_source": alert_data.get("source", "Unknown"),
            "alert_name": alert_data.get("alert_name", "Unknown"),
            "raw_indicators": alert_data.get("indicators", {}),
        },
        "enrichment": {
            "threat_intel": ti_results,
            "mitre_attack": mitre,
        },
        "recommended_actions": [],
        "sla": {
            "acknowledge_by": sev_info["ack_sla"],
            "contain_by": sev_info["contain_sla"],
        },
    }

    if priority in ("P1", "P2"):
        record["recommended_actions"] = [
            "Isolate affected endpoint via EDR",
            "Disable compromised user account",
            "Preserve volatile evidence (memory dump)",
            "Escalate to Tier 2 IR team",
        ]
    else:
        record["recommended_actions"] = [
            "Monitor for additional indicators",
            "Review related alerts for the past 7 days",
            "Document findings and close if benign",
        ]

    with open(output_path, "w") as f:
        json.dump(record, f, indent=2)
    print(f"\n[*] Triage record saved to {output_path}")
    print(f"[*] Ticket: {record['ticket_id']} | Priority: {priority} ({sev_info['label']})")
    return record


def main():
    parser = argparse.ArgumentParser(description="Security Incident Triage Agent")
    parser.add_argument("--alert-name", required=True, help="Name of the triggering alert")
    parser.add_argument("--source", default="SIEM", help="Alert source system")
    parser.add_argument("--severity", default="high", help="Alert severity")
    parser.add_argument("--confidence", type=int, default=75, help="Alert confidence (0-100)")
    parser.add_argument("--host", help="Affected hostname")
    parser.add_argument("--src-ip", help="Source IP address")
    parser.add_argument("--user", help="Affected username")
    parser.add_argument("--process", default="", help="Suspicious process name")
    parser.add_argument("--asset-criticality", default="medium",
                        choices=["critical", "high", "medium", "low"])
    parser.add_argument("--vt-key", help="VirusTotal API key for threat intel")
    parser.add_argument("--indicator", help="IOC to check (IP, hash, or domain)")
    parser.add_argument("--indicator-type", default="ip", choices=["ip", "hash", "domain"])
    parser.add_argument("-o", "--output", default="triage_record.json")
    args = parser.parse_args()

    alert_data = {
        "alert_name": args.alert_name,
        "source": args.source,
        "severity": args.severity,
        "confidence": args.confidence,
        "affected_hosts": [args.host] if args.host else [],
        "affected_users": [args.user] if args.user else [],
        "process": args.process,
        "indicators": {"src_ip": args.src_ip},
        "timestamp": datetime.now(timezone.utc).isoformat(),
    }

    print("[*] Security Incident Triage\n")
    classification = classify_incident(alert_data)
    priority, sev_info = assess_severity(alert_data, args.asset_criticality)
    mitre = build_mitre_mapping(classification[0], args.process)

    ti_results = {}
    if args.vt_key and args.indicator:
        ti_results = check_virustotal(args.vt_key, args.indicator, args.indicator_type)

    generate_triage_record(alert_data, classification, priority, sev_info,
                           ti_results, mitre, args.output)


if __name__ == "__main__":
    main()
Keep exploring