incident response

Performing Active Directory Compromise Investigation

Investigate Active Directory compromise by analyzing authentication logs, replication metadata, Group Policy changes, and Kerberos ticket anomalies to identify attacker persistence and lateral movement paths.

active-directorycompromise-investigationdfirgolden-ticketidentity-forensicskerberoslateral-movementntds-dit
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Active Directory (AD) compromise investigation is a critical incident response capability that focuses on identifying how attackers gained access to domain services, what persistence mechanisms they established, and the scope of credential compromise. Since 88% of breaches involve compromised credentials (Verizon 2025 DBIR), AD is the primary target for enterprise-wide attacks. Investigators must analyze NTDS.dit database integrity, Kerberos ticket-granting activity, Group Policy modifications, replication metadata, and privileged group membership changes to reconstruct the attack chain and determine full compromise scope.

When to Use

  • When conducting security assessments that involve performing active directory compromise investigation
  • 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

  • Familiarity with incident response concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Key Investigation Areas

1. NTDS.dit Database Analysis

The NTDS.dit file is the core Active Directory credential database containing all password hashes for domain accounts. Attackers commonly exfiltrate this file using tools like ntdsutil, secretsdump.py, or DCSync attacks via Mimikatz.

Detection indicators:

  • Event ID 4662: Access to directory service objects with replication permissions
  • Event ID 4742: Computer account modifications on domain controllers
  • Volume Shadow Copy creation on domain controllers (Event ID 8222)
  • Unusual ntdsutil.exe or vssadmin.exe execution
  • Replication traffic from non-DC sources (DCSync detection)

2. Kerberos Attack Detection

Golden Ticket indicators:

  • TGT tickets with abnormally long lifetimes (default is 10 hours)
  • Event ID 4769 with encryption type 0x17 (RC4) instead of AES
  • TGT issued without corresponding Event ID 4768 (AS-REQ)
  • Kerberos tickets referencing non-existent or disabled accounts

Silver Ticket indicators:

  • Service tickets without corresponding TGT requests
  • Event ID 4769 with unusual service names
  • Tickets with forged PAC data

Kerberoasting indicators:

  • High volume of Event ID 4769 for service accounts
  • RC4 encryption requests for accounts that support AES
  • Requests from workstations not normally accessing those services

3. Group Policy Abuse

  • GPO modifications granting new privileges (Event ID 5136)
  • Scheduled task deployment via GPO
  • Software installation policies added to domain
  • Login script modifications
  • Registry-based policy changes for persistence

4. Privileged Group Enumeration

Track modifications to these critical groups:

  • Domain Admins, Enterprise Admins, Schema Admins
  • Account Operators, Backup Operators
  • DnsAdmins (can execute arbitrary DLLs on DCs)
  • Group Policy Creator Owners
  • Protected Users group membership changes

5. Trust Relationship Analysis

  • New forest/domain trusts created (Event ID 4706)
  • SID History injection for privilege escalation
  • Trust ticket forgery indicators
  • Cross-domain authentication anomalies

Investigation Methodology

Phase 1: Scoping and Evidence Collection

1. Identify potentially compromised domain controllers
2. Collect Security, System, Directory Service event logs
3. Extract AD replication metadata using repadmin
4. Capture ntdsutil snapshots for offline analysis
5. Collect DNS server logs and zone transfer records
6. Export Group Policy Object configurations
7. Document current privileged group memberships

Phase 2: Authentication Log Analysis

1. Parse Event ID 4624/4625 for logon patterns
2. Identify pass-the-hash indicators (Event ID 4624 Type 3 with NTLM)
3. Analyze Event ID 4768/4769/4771 for Kerberos anomalies
4. Review Event ID 4776 for NTLM authentication failures
5. Cross-reference logon events with known compromised accounts
6. Map lateral movement paths through authentication chains

Phase 3: Persistence and Backdoor Detection

1. Enumerate AdminSDHolder ACL modifications
2. Check for SID History abuse on accounts
3. Verify krbtgt account password age
4. Audit DSRM password configuration
5. Check for skeleton key malware indicators
6. Review AD Certificate Services for rogue certificates
7. Validate DNS records for poisoning

Phase 4: Remediation Planning

1. Double-rotate krbtgt password (wait replication between rotations)
2. Reset all compromised account passwords
3. Remove unauthorized privileged group members
4. Revoke rogue certificates if AD CS compromised
5. Rebuild domain controllers from clean media if needed
6. Implement tiered administration model
7. Enable Protected Users group for privileged accounts

Critical Event IDs for AD Investigation

Event ID Source Description
4624 Security Successful logon
4625 Security Failed logon
4648 Security Explicit credential logon
4662 Security Operation on AD object
4768 Security Kerberos TGT requested
4769 Security Kerberos service ticket requested
4771 Security Kerberos pre-authentication failed
4776 Security NTLM credential validation
5136 Security Directory object modified
5137 Security Directory object created
4706 Security Trust created
4707 Security Trust removed
4742 Security Computer account changed
8222 System Shadow copy created

Tools for AD Investigation

Tool Purpose
BloodHound Attack path mapping and privilege escalation analysis
Pingcastle AD security assessment and risk scoring
Purple Knight AD vulnerability scanning by Semperis
ADRecon Active Directory data gathering
Mimikatz Credential extraction and Kerberos analysis
Impacket DCSync detection and NTLM relay analysis
Velociraptor Remote forensic artifact collection
Timeline Explorer Event log timeline analysis

MITRE ATT&CK Mapping

Technique ID Relevance
DCSync T1003.006 NTDS.dit credential extraction
Golden Ticket T1558.001 Kerberos TGT forgery
Silver Ticket T1558.002 Service ticket forgery
Kerberoasting T1558.003 Service account hash extraction
Pass-the-Hash T1550.002 NTLM hash reuse
Group Policy Modification T1484.001 Persistence via GPO
Account Manipulation T1098 Privileged group changes
SID-History Injection T1134.005 Privilege escalation

References

Source materials

References and resources

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

References 3

api-reference.md2.6 KB

Active Directory Compromise Investigation - API Reference

Windows Security Event IDs

Event ID Description Compromise Indicator
4662 Directory service object accessed DCSync (replication GUIDs)
4769 Kerberos service ticket requested Kerberoasting (RC4 encryption)
4768 Kerberos TGT requested Golden Ticket (anomalous source)
4672 Special privileges assigned Privileged logon tracking
4624 Successful logon Lateral movement (Type 3)
4648 Explicit credential logon Pass-the-hash, PsExec
4720 User account created Persistence
4728 Member added to global group Privilege escalation
4732 Member added to local group Privilege escalation

DCSync Detection

Replication GUIDs (Event 4662 ObjectType)

GUID Right
1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 DS-Replication-Get-Changes
1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 DS-Replication-Get-Changes-All
89e95b76-444d-4c62-991a-0facbeda640c DS-Replication-Get-Changes-In-Filtered-Set

When a non-DC account triggers 4662 with these GUIDs, it indicates DCSync attack (Mimikatz lsadump::dcsync).

Kerberoasting Detection

Event 4769 with TicketEncryptionType = 0x17 (RC4-HMAC) for service accounts. Normal behavior uses AES (0x11 or 0x12). RC4 requests by user accounts against service SPNs indicate offline cracking attempts.

Golden Ticket Detection

Event 4768 TGT requests from IPs that are not domain controllers. Golden tickets forged offline will show TGT requests from workstations rather than DCs.

Lateral Movement Detection

  • Type 3 logon (Event 4624, LogonType=3): Network logon via SMB, WMI, PsExec
  • Event 4648: Explicit credential use (runas, remote tools)
  • Pattern: Multiple Type 3 logons from same source to different targets

Event Log JSON Format

The agent accepts JSON-exported event logs:

[
  {
    "EventID": 4769,
    "TimeCreated": "2024-01-15T10:30:00Z",
    "EventData": {
      "TargetUserName": "svc_sql",
      "ServiceName": "MSSQLSvc/db01:1433",
      "TicketEncryptionType": "0x17"
    }
  }
]

Export from PowerShell:

Get-WinEvent -LogName Security | ConvertTo-Json -Depth 5 > events.json

Output Schema

{
  "report": "ad_compromise_investigation",
  "total_events_analyzed": 50000,
  "total_findings": 15,
  "severity_summary": {"critical": 3, "high": 7, "medium": 5},
  "findings": [{"type": "dcsync_detected", "severity": "critical"}]
}

CLI Usage

python agent.py --log events.json --output report.json
standards.md2.6 KB

Standards and Frameworks for AD Compromise Investigation

NIST SP 800-61 Rev 2 - Computer Security Incident Handling Guide

  • Provides incident response lifecycle: Preparation, Detection & Analysis, Containment, Eradication & Recovery, Post-Incident Activity
  • AD compromise investigations follow all four phases with emphasis on scoping the identity compromise

CISA Alert: Detecting and Mitigating Active Directory Compromises

MITRE ATT&CK Framework - Credential Access Tactics

  • T1003.006: OS Credential Dumping - DCSync
  • T1558.001: Steal or Forge Kerberos Tickets - Golden Ticket
  • T1558.002: Steal or Forge Kerberos Tickets - Silver Ticket
  • T1558.003: Steal or Forge Kerberos Tickets - Kerberoasting
  • T1550.002: Use Alternate Authentication Material - Pass the Hash
  • T1484.001: Domain Policy Modification - Group Policy Modification
  • T1098: Account Manipulation

Microsoft Security Best Practices

CIS Benchmarks for Active Directory

  • CIS Microsoft Windows Server Benchmark for DC hardening
  • Password policy requirements (minimum 14 characters)
  • Account lockout policy configuration
  • Audit policy settings for security event logging
  • GPO security baseline configurations

SANS FOR500 - Windows Forensic Analysis

  • Windows artifact analysis methodology
  • Registry analysis for persistence detection
  • Event log forensics for authentication tracking
  • Timeline analysis techniques for AD compromise

Semperis Identity Forensics and Incident Response (IFIR)

Key Windows Security Event IDs for AD Monitoring

  • Microsoft documentation on security auditing events
  • Advanced Audit Policy Configuration for DCs
  • Kerberos event logging requirements
  • Directory Service Access auditing
workflows.md4.0 KB

AD Compromise Investigation Workflows

Workflow 1: Initial Triage and Scoping

START: AD Compromise Alert Received
  |
  v
[Determine Scope of Potential Compromise]
  |-- Single account compromised?
  |-- Multiple accounts compromised?
  |-- Domain controller compromised?
  |-- Full domain compromise suspected?
  |
  v
[Collect Initial Evidence]
  |-- Export Security event logs from all DCs
  |-- Snapshot AD replication metadata
  |-- Export current GPO configurations
  |-- Document privileged group memberships
  |
  v
[Identify Compromised Accounts]
  |-- Analyze logon events (4624/4625)
  |-- Check for pass-the-hash patterns
  |-- Review Kerberos ticket activity
  |-- Cross-reference with threat intelligence
  |
  v
[Determine Attack Timeline]
  |-- First known malicious activity
  |-- Privilege escalation events
  |-- Lateral movement chain
  |-- Data access and exfiltration
  |
  v
[Report Findings and Escalate]

Workflow 2: Kerberos Attack Investigation

START: Suspicious Kerberos Activity Detected
  |
  v
[Analyze Event ID 4768 - TGT Requests]
  |-- Normal TGT requests from user workstations?
  |-- TGT requests from unusual sources?
  |-- Encryption type analysis (AES vs RC4)
  |
  v
[Analyze Event ID 4769 - Service Ticket Requests]
  |-- High volume from single source? --> Kerberoasting
  |-- Service tickets without prior TGT? --> Silver Ticket
  |-- Unusual service names or SPNs?
  |
  v
[Check for Golden Ticket Indicators]
  |-- TGT lifetime exceeds 10 hours?
  |-- TGT for non-existent accounts?
  |-- TGT without matching AS-REQ?
  |-- krbtgt account age analysis
  |
  v
[Determine Remediation Actions]
  |-- Kerberoasting: Reset affected service account passwords
  |-- Golden Ticket: Double-rotate krbtgt password
  |-- Silver Ticket: Reset affected service account passwords
  |-- All: Review and harden SPN configurations
  |
  v
END: Remediation Complete + Monitoring Enhanced

Workflow 3: DCSync Attack Investigation

START: Replication from Non-DC Source Detected
  |
  v
[Verify Replication Source]
  |-- Is source a legitimate domain controller?
  |-- Check Event ID 4662 for replication rights usage
  |-- Verify account has DS-Replication-Get-Changes-All
  |
  v
[If Unauthorized Replication Detected]
  |-- Identify compromised account with replication rights
  |-- Determine which credentials were replicated
  |-- Check for Mimikatz/Impacket artifacts on source
  |
  v
[Assess Credential Exposure]
  |-- All domain password hashes potentially compromised
  |-- krbtgt hash exposure enables Golden Ticket
  |-- Service account hashes enable Silver Tickets
  |-- Trust keys enable cross-domain attacks
  |
  v
[Execute Full Credential Reset]
  |-- Double-rotate krbtgt (wait for replication)
  |-- Reset all privileged account passwords
  |-- Reset all service account passwords
  |-- Force password change for all users
  |
  v
END: Monitor for Continued Unauthorized Replication

Workflow 4: Post-Compromise Remediation

START: Compromise Scope Determined
  |
  v
[Immediate Containment]
  |-- Disable compromised accounts
  |-- Block attacker IP addresses
  |-- Isolate compromised systems
  |-- Revoke active sessions and tokens
  |
  v
[Credential Reset Sequence]
  |-- Step 1: Reset krbtgt (first rotation)
  |-- Step 2: Wait for full AD replication
  |-- Step 3: Reset krbtgt (second rotation)
  |-- Step 4: Wait for full AD replication
  |-- Step 5: Reset all privileged accounts
  |-- Step 6: Reset all service accounts
  |-- Step 7: Force user password changes
  |
  v
[Persistence Removal]
  |-- Remove unauthorized GPO modifications
  |-- Clean AdminSDHolder ACL entries
  |-- Remove SID History entries
  |-- Revoke rogue AD CS certificates
  |-- Remove skeleton key if detected
  |-- Clean scheduled tasks and services
  |
  v
[Hardening Implementation]
  |-- Deploy tiered administration model
  |-- Enable Protected Users group
  |-- Implement PAW for admin tasks
  |-- Deploy LAPS for local admin
  |-- Configure advanced audit policies
  |
  v
END: Continuous Monitoring Established

Scripts 2

agent.py8.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Active Directory Compromise Investigation agent - analyzes Windows Security
event logs for indicators of AD compromise including DCSync, Golden Ticket,
Kerberoasting, and lateral movement."""

import argparse
import json
from collections import Counter, defaultdict
from datetime import datetime
from pathlib import Path


COMPROMISE_INDICATORS = {
    4662: {"name": "DCSync Detection", "description": "Directory service object accessed with replication rights"},
    4769: {"name": "Kerberoasting", "description": "Kerberos service ticket requested with RC4 encryption"},
    4768: {"name": "Golden Ticket", "description": "Kerberos TGT requested"},
    4672: {"name": "Privileged Logon", "description": "Special privileges assigned to new logon"},
    4624: {"name": "Logon Event", "description": "Successful logon - check Type 3 for lateral movement"},
    4648: {"name": "Explicit Credential Logon", "description": "Logon using explicit credentials"},
    4720: {"name": "Account Created", "description": "New user account created"},
    4728: {"name": "Group Membership Change", "description": "Member added to global group"},
}


def parse_evtx_json(log_path: str) -> list[dict]:
    """Parse Windows event logs exported as JSON."""
    content = Path(log_path).read_text(encoding="utf-8")
    try:
        events = json.loads(content)
    except json.JSONDecodeError:
        events = [json.loads(line) for line in content.strip().splitlines() if line.strip()]
    return events


def detect_dcsync(events: list[dict]) -> list[dict]:
    """Detect DCSync attacks via Event ID 4662 with replication GUIDs."""
    replication_guids = {
        "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2",
        "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2",
        "89e95b76-444d-4c62-991a-0facbeda640c",
    }
    findings = []
    for evt in events:
        eid = evt.get("EventID") or evt.get("event_id")
        if eid != 4662:
            continue
        data = evt.get("EventData", evt.get("event_data", {}))
        obj_type = str(data.get("ObjectType", "")).lower().strip("{}")
        if obj_type in replication_guids:
            subject = data.get("SubjectUserName", "unknown")
            findings.append({
                "type": "dcsync_detected",
                "severity": "critical",
                "event_id": 4662,
                "subject": subject,
                "object_type": obj_type,
                "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")),
                "detail": f"DCSync replication by {subject}",
            })
    return findings


def detect_kerberoasting(events: list[dict]) -> list[dict]:
    """Detect Kerberoasting via Event ID 4769 with RC4 encryption."""
    findings = []
    for evt in events:
        eid = evt.get("EventID") or evt.get("event_id")
        if eid != 4769:
            continue
        data = evt.get("EventData", evt.get("event_data", {}))
        ticket_encryption = str(data.get("TicketEncryptionType", ""))
        if ticket_encryption == "0x17":
            service = data.get("ServiceName", "unknown")
            client = data.get("TargetUserName", "unknown")
            findings.append({
                "type": "kerberoasting_detected",
                "severity": "high",
                "event_id": 4769,
                "service": service,
                "client": client,
                "encryption": "RC4 (0x17)",
                "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")),
                "detail": f"RC4 TGS request for {service} by {client}",
            })
    return findings


def detect_golden_ticket(events: list[dict]) -> list[dict]:
    """Detect Golden Ticket indicators via Event ID 4768 anomalies."""
    findings = []
    for evt in events:
        eid = evt.get("EventID") or evt.get("event_id")
        if eid != 4768:
            continue
        data = evt.get("EventData", evt.get("event_data", {}))
        client = data.get("TargetUserName", "")
        ip_addr = data.get("IpAddress", "")
        if ip_addr and not ip_addr.startswith("::") and ip_addr != "127.0.0.1":
            findings.append({
                "type": "golden_ticket_indicator",
                "severity": "critical",
                "event_id": 4768,
                "client": client,
                "source_ip": ip_addr,
                "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")),
                "detail": f"TGT request for {client} from {ip_addr}",
            })
    return findings


def detect_lateral_movement(events: list[dict]) -> list[dict]:
    """Detect lateral movement via Type 3 logons and explicit credential use."""
    findings = []
    lateral_events = defaultdict(list)
    for evt in events:
        eid = evt.get("EventID") or evt.get("event_id")
        data = evt.get("EventData", evt.get("event_data", {}))
        if eid == 4624:
            logon_type = str(data.get("LogonType", ""))
            if logon_type == "3":
                src_ip = data.get("IpAddress", "")
                user = data.get("TargetUserName", "")
                lateral_events[(user, src_ip)].append(
                    evt.get("TimeCreated", evt.get("timestamp", "")))
        elif eid == 4648:
            user = data.get("SubjectUserName", "")
            target = data.get("TargetServerName", "")
            findings.append({
                "type": "explicit_credential_use",
                "severity": "medium",
                "event_id": 4648,
                "user": user,
                "target_server": target,
                "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")),
                "detail": f"Explicit credential logon by {user} to {target}",
            })

    for (user, src_ip), timestamps in lateral_events.items():
        if len(timestamps) > 5:
            findings.append({
                "type": "lateral_movement_pattern",
                "severity": "high",
                "user": user,
                "source_ip": src_ip,
                "logon_count": len(timestamps),
                "detail": f"{len(timestamps)} Type 3 logons by {user} from {src_ip}",
            })
    return findings


def detect_persistence(events: list[dict]) -> list[dict]:
    """Detect persistence mechanisms via account creation and group changes."""
    findings = []
    for evt in events:
        eid = evt.get("EventID") or evt.get("event_id")
        data = evt.get("EventData", evt.get("event_data", {}))
        if eid == 4720:
            creator = data.get("SubjectUserName", "unknown")
            new_account = data.get("TargetUserName", "unknown")
            findings.append({
                "type": "account_created",
                "severity": "medium",
                "event_id": 4720,
                "creator": creator,
                "new_account": new_account,
                "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")),
                "detail": f"Account {new_account} created by {creator}",
            })
        elif eid in (4728, 4732):
            subject = data.get("SubjectUserName", "unknown")
            member = data.get("MemberName", "unknown")
            group = data.get("TargetUserName", "unknown")
            findings.append({
                "type": "group_membership_change",
                "severity": "high",
                "event_id": eid,
                "subject": subject,
                "member": member,
                "group": group,
                "timestamp": evt.get("TimeCreated", evt.get("timestamp", "")),
                "detail": f"{member} added to {group} by {subject}",
            })
    return findings


def generate_report(log_path: str) -> dict:
    """Run all detection checks and produce consolidated report."""
    events = parse_evtx_json(log_path)
    findings = []
    findings.extend(detect_dcsync(events))
    findings.extend(detect_kerberoasting(events))
    findings.extend(detect_golden_ticket(events))
    findings.extend(detect_lateral_movement(events))
    findings.extend(detect_persistence(events))

    severity_counts = Counter(f["severity"] for f in findings)
    return {
        "report": "ad_compromise_investigation",
        "generated_at": datetime.utcnow().isoformat() + "Z",
        "log_file": log_path,
        "total_events_analyzed": len(events),
        "total_findings": len(findings),
        "severity_summary": dict(severity_counts),
        "findings": findings,
    }


def main():
    parser = argparse.ArgumentParser(description="AD Compromise Investigation Agent")
    parser.add_argument("--log", required=True, help="Path to JSON-exported Windows Security event log")
    parser.add_argument("--output", help="Output JSON file path")
    args = parser.parse_args()

    report = generate_report(args.log)
    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.py22.3 KB
Display-only source. This catalog never executes bundled scripts.
"""
Active Directory Compromise Investigation Script
Analyzes Windows Security event logs for indicators of AD compromise
including DCSync, Golden Ticket, Kerberoasting, and lateral movement.
"""

import json
import csv
import os
import re
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta
from collections import defaultdict, Counter
from pathlib import Path


class ADCompromiseInvestigator:
    """Investigates Active Directory compromise through event log analysis."""

    # Critical Event IDs for AD investigation
    EVENT_IDS = {
        4624: "Successful Logon",
        4625: "Failed Logon",
        4648: "Explicit Credential Logon",
        4662: "AD Object Operation",
        4768: "Kerberos TGT Request (AS-REQ)",
        4769: "Kerberos Service Ticket Request (TGS-REQ)",
        4771: "Kerberos Pre-Auth Failed",
        4776: "NTLM Credential Validation",
        5136: "Directory Object Modified",
        5137: "Directory Object Created",
        4706: "Trust Created",
        4707: "Trust Removed",
        4728: "Member Added to Security-Enabled Global Group",
        4732: "Member Added to Security-Enabled Local Group",
        4756: "Member Added to Security-Enabled Universal Group",
        4742: "Computer Account Changed",
        8222: "Shadow Copy Created",
    }

    PRIVILEGED_GROUPS = [
        "Domain Admins",
        "Enterprise Admins",
        "Schema Admins",
        "Account Operators",
        "Backup Operators",
        "DnsAdmins",
        "Group Policy Creator Owners",
        "Server Operators",
        "Print Operators",
        "Protected Users",
    ]

    DCSYNC_GUIDS = [
        "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2",  # DS-Replication-Get-Changes
        "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2",  # DS-Replication-Get-Changes-All
        "89e95b76-444d-4c62-991a-0facbeda640c",  # DS-Replication-Get-Changes-In-Filtered-Set
    ]

    def __init__(self, output_dir="ad_investigation_results"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []
        self.compromised_accounts = set()
        self.lateral_movement_chain = []
        self.kerberos_anomalies = []
        self.privilege_escalations = []

    def parse_evtx_xml(self, xml_file):
        """Parse exported Windows event log XML file."""
        events = []
        try:
            tree = ET.parse(xml_file)
            root = tree.getroot()
            ns = {"ns": "http://schemas.microsoft.com/win/2004/08/events/event"}

            for event_elem in root.findall(".//ns:Event", ns):
                event = self._extract_event_data(event_elem, ns)
                if event:
                    events.append(event)
        except ET.ParseError as e:
            print(f"[ERROR] XML parse error in {xml_file}: {e}")
        return events

    def _extract_event_data(self, event_elem, ns):
        """Extract structured data from an event XML element."""
        system = event_elem.find("ns:System", ns)
        if system is None:
            return None

        event_id_elem = system.find("ns:EventID", ns)
        time_elem = system.find("ns:TimeCreated", ns)
        computer_elem = system.find("ns:Computer", ns)

        event = {
            "event_id": int(event_id_elem.text) if event_id_elem is not None else 0,
            "timestamp": time_elem.get("SystemTime", "") if time_elem is not None else "",
            "computer": computer_elem.text if computer_elem is not None else "",
            "data": {},
        }

        event_data = event_elem.find("ns:EventData", ns)
        if event_data is not None:
            for data in event_data.findall("ns:Data", ns):
                name = data.get("Name", "")
                value = data.text or ""
                event[" data"][name] = value

        return event

    def analyze_dcsync_activity(self, events):
        """Detect DCSync attacks by analyzing replication permission usage."""
        print("[*] Analyzing for DCSync activity...")
        dcsync_events = []

        for event in events:
            if event["event_id"] != 4662:
                continue
            properties = event["data"].get("Properties", "")
            for guid in self.DCSYNC_GUIDS:
                if guid.lower() in properties.lower():
                    subject = event["data"].get("SubjectUserName", "Unknown")
                    dcsync_events.append({
                        "timestamp": event["timestamp"],
                        "account": subject,
                        "computer": event["computer"],
                        "guid": guid,
                        "severity": "CRITICAL",
                        "description": f"DCSync replication rights used by {subject}",
                    })
                    self.compromised_accounts.add(subject)

        if dcsync_events:
            finding = {
                "category": "DCSync Attack",
                "severity": "CRITICAL",
                "count": len(dcsync_events),
                "details": dcsync_events,
                "recommendation": "Immediately revoke replication rights from non-DC accounts. "
                                  "Double-rotate krbtgt password. Reset all domain passwords.",
            }
            self.findings.append(finding)
            print(f"  [!] CRITICAL: {len(dcsync_events)} DCSync events detected!")
        else:
            print("  [+] No DCSync activity detected.")

        return dcsync_events

    def analyze_golden_ticket_indicators(self, events):
        """Detect Golden Ticket usage through Kerberos anomalies."""
        print("[*] Analyzing for Golden Ticket indicators...")
        golden_ticket_indicators = []

        tgt_requests = {}
        service_tickets = []

        for event in events:
            if event["event_id"] == 4768:
                account = event["data"].get("TargetUserName", "")
                tgt_requests[account] = event
            elif event["event_id"] == 4769:
                service_tickets.append(event)

        for ticket in service_tickets:
            account = ticket["data"].get("TargetUserName", "")
            encryption = ticket["data"].get("TicketEncryptionType", "")
            # RC4 encryption (0x17) when AES should be used indicates forged ticket
            if encryption == "0x17":
                golden_ticket_indicators.append({
                    "timestamp": ticket["timestamp"],
                    "account": account,
                    "indicator": "RC4 encryption on service ticket (should be AES)",
                    "encryption_type": encryption,
                    "severity": "HIGH",
                })
            # Service ticket without corresponding TGT request
            if account and account not in tgt_requests:
                golden_ticket_indicators.append({
                    "timestamp": ticket["timestamp"],
                    "account": account,
                    "indicator": "Service ticket without matching TGT request",
                    "severity": "CRITICAL",
                })

        if golden_ticket_indicators:
            finding = {
                "category": "Golden Ticket Indicators",
                "severity": "CRITICAL",
                "count": len(golden_ticket_indicators),
                "details": golden_ticket_indicators,
                "recommendation": "Double-rotate krbtgt password immediately. "
                                  "Wait for full replication between rotations.",
            }
            self.findings.append(finding)
            print(f"  [!] CRITICAL: {len(golden_ticket_indicators)} Golden Ticket indicators!")
        else:
            print("  [+] No Golden Ticket indicators detected.")

        return golden_ticket_indicators

    def analyze_kerberoasting(self, events):
        """Detect Kerberoasting by analyzing bulk service ticket requests."""
        print("[*] Analyzing for Kerberoasting activity...")
        tgs_requests_by_source = defaultdict(list)

        for event in events:
            if event["event_id"] != 4769:
                continue
            source_ip = event["data"].get("IpAddress", "")
            service_name = event["data"].get("ServiceName", "")
            encryption = event["data"].get("TicketEncryptionType", "")
            account = event["data"].get("TargetUserName", "")

            if service_name and not service_name.endswith("$"):
                tgs_requests_by_source[source_ip].append({
                    "timestamp": event["timestamp"],
                    "service": service_name,
                    "account": account,
                    "encryption": encryption,
                })

        kerberoasting_sources = []
        for source_ip, requests in tgs_requests_by_source.items():
            # Threshold: more than 5 unique service tickets in short timeframe
            unique_services = set(r["service"] for r in requests)
            if len(unique_services) >= 5:
                kerberoasting_sources.append({
                    "source_ip": source_ip,
                    "unique_services_requested": len(unique_services),
                    "total_requests": len(requests),
                    "services": list(unique_services)[:20],
                    "severity": "HIGH",
                })

        if kerberoasting_sources:
            finding = {
                "category": "Kerberoasting Activity",
                "severity": "HIGH",
                "count": len(kerberoasting_sources),
                "details": kerberoasting_sources,
                "recommendation": "Reset passwords of targeted service accounts. "
                                  "Use Group Managed Service Accounts (gMSA) where possible. "
                                  "Enforce long, complex passwords on service accounts.",
            }
            self.findings.append(finding)
            print(f"  [!] HIGH: {len(kerberoasting_sources)} sources performing Kerberoasting!")
        else:
            print("  [+] No Kerberoasting activity detected.")

        return kerberoasting_sources

    def analyze_pass_the_hash(self, events):
        """Detect Pass-the-Hash through NTLM logon analysis."""
        print("[*] Analyzing for Pass-the-Hash indicators...")
        pth_indicators = []

        for event in events:
            if event["event_id"] != 4624:
                continue
            logon_type = event["data"].get("LogonType", "")
            auth_package = event["data"].get("AuthenticationPackageName", "")
            logon_process = event["data"].get("LogonProcessName", "")

            # Type 3 (Network) + NTLM + NtLmSsp = potential PtH
            if logon_type == "3" and "NTLM" in auth_package and "NtLmSsp" in logon_process:
                account = event["data"].get("TargetUserName", "")
                source_ip = event["data"].get("IpAddress", "")
                workstation = event["data"].get("WorkstationName", "")

                pth_indicators.append({
                    "timestamp": event["timestamp"],
                    "account": account,
                    "source_ip": source_ip,
                    "workstation": workstation,
                    "severity": "HIGH",
                    "description": f"NTLM network logon from {source_ip} as {account}",
                })

        if pth_indicators:
            finding = {
                "category": "Pass-the-Hash Indicators",
                "severity": "HIGH",
                "count": len(pth_indicators),
                "details": pth_indicators[:50],
                "recommendation": "Enable Credential Guard. Enforce NTLMv2 only. "
                                  "Add privileged accounts to Protected Users group. "
                                  "Implement tiered administration.",
            }
            self.findings.append(finding)
            print(f"  [!] HIGH: {len(pth_indicators)} Pass-the-Hash indicators detected!")
        else:
            print("  [+] No Pass-the-Hash indicators detected.")

        return pth_indicators

    def analyze_privilege_escalation(self, events):
        """Detect unauthorized additions to privileged groups."""
        print("[*] Analyzing for privilege escalation via group membership...")
        escalations = []

        group_change_events = [4728, 4732, 4756]

        for event in events:
            if event["event_id"] not in group_change_events:
                continue
            group_name = event["data"].get("TargetUserName", "")
            member_added = event["data"].get("MemberName", "")
            changed_by = event["data"].get("SubjectUserName", "")

            for priv_group in self.PRIVILEGED_GROUPS:
                if priv_group.lower() in group_name.lower():
                    escalations.append({
                        "timestamp": event["timestamp"],
                        "group": group_name,
                        "member_added": member_added,
                        "changed_by": changed_by,
                        "computer": event["computer"],
                        "severity": "CRITICAL",
                    })

        self.privilege_escalations = escalations

        if escalations:
            finding = {
                "category": "Privileged Group Modification",
                "severity": "CRITICAL",
                "count": len(escalations),
                "details": escalations,
                "recommendation": "Review all privileged group membership changes. "
                                  "Remove unauthorized members. Audit AdminSDHolder.",
            }
            self.findings.append(finding)
            print(f"  [!] CRITICAL: {len(escalations)} privileged group modifications!")
        else:
            print("  [+] No suspicious privileged group modifications detected.")

        return escalations

    def analyze_lateral_movement(self, events):
        """Map lateral movement chains through authentication patterns."""
        print("[*] Analyzing lateral movement patterns...")
        auth_chain = defaultdict(lambda: defaultdict(set))

        for event in events:
            if event["event_id"] != 4624:
                continue
            logon_type = event["data"].get("LogonType", "")
            if logon_type not in ("3", "10"):
                continue
            account = event["data"].get("TargetUserName", "")
            source_ip = event["data"].get("IpAddress", "")
            target = event["computer"]

            if source_ip and account and not account.endswith("$"):
                auth_chain[account][source_ip].add(target)

        movement_paths = []
        for account, sources in auth_chain.items():
            for source_ip, targets in sources.items():
                if len(targets) >= 3:
                    movement_paths.append({
                        "account": account,
                        "source_ip": source_ip,
                        "targets_accessed": list(targets),
                        "target_count": len(targets),
                        "severity": "HIGH" if len(targets) >= 5 else "MEDIUM",
                    })

        if movement_paths:
            finding = {
                "category": "Lateral Movement Patterns",
                "severity": "HIGH",
                "count": len(movement_paths),
                "details": sorted(movement_paths, key=lambda x: x["target_count"], reverse=True)[:20],
                "recommendation": "Investigate accounts with wide lateral movement. "
                                  "Implement network segmentation. Enable credential guard.",
            }
            self.findings.append(finding)
            print(f"  [!] HIGH: {len(movement_paths)} lateral movement patterns detected!")
        else:
            print("  [+] No significant lateral movement patterns detected.")

        return movement_paths

    def analyze_gpo_modifications(self, events):
        """Detect suspicious Group Policy Object modifications."""
        print("[*] Analyzing GPO modifications...")
        gpo_changes = []

        for event in events:
            if event["event_id"] != 5136:
                continue
            object_class = event["data"].get("ObjectClass", "")
            if "grouppolicycontainer" in object_class.lower():
                changed_by = event["data"].get("SubjectUserName", "")
                attribute = event["data"].get("AttributeLDAPDisplayName", "")
                value = event["data"].get("AttributeValue", "")

                gpo_changes.append({
                    "timestamp": event["timestamp"],
                    "changed_by": changed_by,
                    "attribute": attribute,
                    "value": value[:200],
                    "computer": event["computer"],
                    "severity": "HIGH",
                })

        if gpo_changes:
            finding = {
                "category": "GPO Modifications",
                "severity": "HIGH",
                "count": len(gpo_changes),
                "details": gpo_changes,
                "recommendation": "Review all GPO changes for unauthorized modifications. "
                                  "Check for malicious scheduled tasks, login scripts, "
                                  "or software installation policies.",
            }
            self.findings.append(finding)
            print(f"  [!] HIGH: {len(gpo_changes)} GPO modifications detected!")
        else:
            print("  [+] No GPO modifications detected.")

        return gpo_changes

    def generate_report(self):
        """Generate comprehensive investigation report."""
        report = {
            "investigation_report": "Active Directory Compromise Investigation",
            "generated_at": datetime.utcnow().isoformat(),
            "summary": {
                "total_findings": len(self.findings),
                "critical_findings": sum(1 for f in self.findings if f["severity"] == "CRITICAL"),
                "high_findings": sum(1 for f in self.findings if f["severity"] == "HIGH"),
                "compromised_accounts": list(self.compromised_accounts),
            },
            "findings": self.findings,
            "recommendations": self._generate_recommendations(),
        }

        report_path = self.output_dir / "ad_investigation_report.json"
        with open(report_path, "w") as f:
            json.dump(report, f, indent=2, default=str)

        print(f"\n[+] Report saved to {report_path}")
        self._print_summary(report)
        return report

    def _generate_recommendations(self):
        """Generate prioritized remediation recommendations."""
        recommendations = []
        has_critical = any(f["severity"] == "CRITICAL" for f in self.findings)

        if has_critical:
            recommendations.append({
                "priority": 1,
                "action": "Double-rotate krbtgt password",
                "detail": "Reset krbtgt twice with full AD replication between rotations. "
                          "This invalidates all Kerberos tickets including Golden Tickets.",
            })
            recommendations.append({
                "priority": 2,
                "action": "Reset all privileged account passwords",
                "detail": "Reset passwords for Domain Admins, Enterprise Admins, "
                          "and all service accounts with SPNs.",
            })

        if self.compromised_accounts:
            recommendations.append({
                "priority": 3,
                "action": "Disable and investigate compromised accounts",
                "detail": f"Accounts to investigate: {', '.join(self.compromised_accounts)}",
            })

        recommendations.extend([
            {
                "priority": 4,
                "action": "Implement tiered administration model",
                "detail": "Separate Tier 0 (AD), Tier 1 (servers), Tier 2 (workstations) "
                          "with no credential reuse across tiers.",
            },
            {
                "priority": 5,
                "action": "Deploy advanced monitoring",
                "detail": "Enable Microsoft Defender for Identity or equivalent. "
                          "Configure advanced audit policies on all DCs.",
            },
        ])

        return recommendations

    def _print_summary(self, report):
        """Print investigation summary to console."""
        summary = report["summary"]
        print("\n" + "=" * 60)
        print("AD COMPROMISE INVESTIGATION SUMMARY")
        print("=" * 60)
        print(f"Total Findings: {summary['total_findings']}")
        print(f"Critical: {summary['critical_findings']}")
        print(f"High: {summary['high_findings']}")
        print(f"Compromised Accounts: {len(summary['compromised_accounts'])}")
        if summary["compromised_accounts"]:
            for account in summary["compromised_accounts"]:
                print(f"  - {account}")
        print("=" * 60)

    def run_full_investigation(self, event_log_files):
        """Run complete AD compromise investigation."""
        print("[*] Starting Active Directory Compromise Investigation")
        print(f"[*] Processing {len(event_log_files)} event log files...")

        all_events = []
        for log_file in event_log_files:
            events = self.parse_evtx_xml(log_file)
            all_events.extend(events)
            print(f"  Parsed {len(events)} events from {log_file}")

        print(f"[*] Total events to analyze: {len(all_events)}")

        self.analyze_dcsync_activity(all_events)
        self.analyze_golden_ticket_indicators(all_events)
        self.analyze_kerberoasting(all_events)
        self.analyze_pass_the_hash(all_events)
        self.analyze_privilege_escalation(all_events)
        self.analyze_lateral_movement(all_events)
        self.analyze_gpo_modifications(all_events)

        return self.generate_report()


def main():
    import argparse

    parser = argparse.ArgumentParser(
        description="Active Directory Compromise Investigation Tool"
    )
    parser.add_argument(
        "log_files",
        nargs="+",
        help="Windows Security event log XML files to analyze",
    )
    parser.add_argument(
        "-o", "--output",
        default="ad_investigation_results",
        help="Output directory for investigation results",
    )

    args = parser.parse_args()

    investigator = ADCompromiseInvestigator(output_dir=args.output)
    report = investigator.run_full_investigation(args.log_files)

    if report["summary"]["critical_findings"] > 0:
        print("\n[!!!] CRITICAL FINDINGS DETECTED - IMMEDIATE ACTION REQUIRED")
        exit(1)


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 3.7 KB
Keep exploring