Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
NIST CSF 2.0
MITRE D3FEND
When to Use
- When hunting for DCSync credential theft (MITRE ATT&CK T1003.006)
- After detecting Mimikatz or similar tools in the environment
- During incident response involving Active Directory compromise
- When monitoring for unauthorized domain replication requests
- During purple team exercises testing AD attack detection
Prerequisites
- Windows Security Event Log forwarding enabled (Event ID 4662)
- Audit Directory Service Access enabled via Group Policy
- Domain Computers SACL configured on Domain Object for machine account detection
- SIEM with Windows event data ingested (Splunk, Elastic, Sentinel)
- Knowledge of legitimate domain controller accounts and replication partners
Workflow
- Enable Auditing: Ensure Audit Directory Service Access is enabled on domain controllers.
- Collect Events: Gather Windows Event ID 4662 with AccessMask 0x100 (Control Access).
- Filter Replication GUIDs: Search for DS-Replication-Get-Changes and DS-Replication-Get-Changes-All.
- Identify Non-DC Sources: Flag events where SubjectUserName is not a domain controller machine account.
- Correlate with Network: Cross-reference source IPs against known DC addresses.
- Validate Findings: Exclude legitimate replication tools (Azure AD Connect, SCCM).
- Respond: Disable compromised accounts, reset krbtgt, investigate lateral movement.
Key Concepts
| Concept | Description |
|---|---|
| DCSync | Technique abusing AD replication protocol to extract password hashes |
| Event ID 4662 | Directory Service Access audit event |
| DS-Replication-Get-Changes | GUID 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 |
| DS-Replication-Get-Changes-All | GUID 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 |
| AccessMask 0x100 | Control Access right indicating extended rights verification |
| T1003.006 | OS Credential Dumping: DCSync |
Tools & Systems
| Tool | Purpose |
|---|---|
| Windows Event Viewer | Direct event log analysis |
| Splunk | SIEM correlation of Event 4662 |
| Elastic Security | Detection rules for DCSync patterns |
| Mimikatz lsadump::dcsync | Attack tool used to perform DCSync |
| Impacket secretsdump.py | Python-based DCSync implementation |
| BloodHound | Identify accounts with replication rights |
Output Format
Hunt ID: TH-DCSYNC-[DATE]-[SEQ]
Technique: T1003.006
Domain Controller: [DC hostname]
Subject Account: [Account performing replication]
Source IP: [Non-DC IP address]
GUID Accessed: [Replication GUID]
Risk Level: [Critical/High/Medium/Low]
Recommended Action: [Disable account, reset krbtgt, investigate]Source materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md3.4 KB
DCSync Attack Detection Reference
Windows Event ID 4662
Directory Service Access event logged when an object in Active Directory is accessed.
Required Group Policy Configuration
Computer Configuration > Policies > Windows Settings > Security Settings >
Advanced Audit Policy Configuration > Audit Policies > DS Access >
Audit Directory Service Access: Success, FailureRequired SACL Configuration
Add "Domain Computers" to the SACL on the domain root object to detect machine account DCSync.
Key Detection GUIDs
| GUID | Right | Description |
|---|---|---|
| 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 | DS-Replication-Get-Changes | Read replication changes |
| 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 | DS-Replication-Get-Changes-All | Read all replication changes (includes secrets) |
| 89e95b76-444d-4c62-991a-0facbeda640c | DS-Replication-Get-Changes-In-Filtered-Set | Filtered replication set |
AccessMask Value
0x100 (256 decimal) = Control Access - logged when access is allowed following extended rights verification.
Splunk Detection Query
index=wineventlog EventCode=4662
| where AccessMask="0x100"
| where match(Properties, "(?i)1131f6ad-9c07-11d1-f79f-00c04fc2dcd2") OR match(Properties, "(?i)1131f6aa-9c07-11d1-f79f-00c04fc2dcd2")
| where NOT match(SubjectUserName, "\\$$")
| eval is_dc=if(match(SubjectUserName, "(?i)(DC|AZUREADCONNECT)"), "legitimate", "suspicious")
| where is_dc="suspicious"
| stats count by SubjectUserName, SubjectDomainName, Computer, PropertiesElastic KQL Detection
event.code: "4662" AND winlog.event_data.AccessMask: "0x100" AND
winlog.event_data.Properties: (*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2* OR *1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*)
AND NOT winlog.event_data.SubjectUserName: *$PowerShell Detection
# Query Event 4662 for replication GUID access
Get-WinEvent -FilterHashtable @{
LogName='Security'; Id=4662
} | Where-Object {
$_.Properties[8].Value -match '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2' -and
$_.Properties[1].Value -notmatch '\$$'
} | Select-Object TimeCreated,
@{N='Account';E={$_.Properties[1].Value}},
@{N='Domain';E={$_.Properties[2].Value}}
# List accounts with replication rights
Import-Module ActiveDirectory
(Get-Acl "AD:\DC=domain,DC=local").Access |
Where-Object { $_.ObjectType -in @(
'1131f6ad-9c07-11d1-f79f-00c04fc2dcd2',
'1131f6aa-9c07-11d1-f79f-00c04fc2dcd2'
)} | Select-Object IdentityReference, ActiveDirectoryRightsAttack Tools (for Detection Signatures)
# Mimikatz DCSync
lsadump::dcsync /domain:corp.local /user:krbtgt
# Impacket secretsdump.py
secretsdump.py -just-dc corp.local/admin:Password@dc01.corp.local
# Impacket - specific user
secretsdump.py -just-dc-user krbtgt corp.local/admin:Password@dc01.corp.localMITRE ATT&CK Mapping
- Technique: T1003.006 - OS Credential Dumping: DCSync
- Tactic: Credential Access
- Platforms: Windows
- Data Sources: Active Directory: Active Directory Object Access, Network Traffic
Response Checklist
- Disable compromised account immediately
- Reset krbtgt password twice (12-hour interval between resets)
- Revoke all Kerberos tickets (purge ticket cache)
- Audit all accounts with replication rights on domain object
- Review source host for additional compromise indicators
- Check for persistence mechanisms (scheduled tasks, services, WMI)
Scripts 1
agent.py9.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""DCSync Detection Agent - hunts for unauthorized AD replication requests via Event ID 4662 analysis."""
import json
import argparse
import logging
import subprocess
import re
import xml.etree.ElementTree as ET
from collections import defaultdict
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
REPLICATION_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",
}
DCSYNC_ACCESS_MASK = "0x100"
def get_domain_controllers():
"""Get list of legitimate domain controller machine accounts."""
cmd = ["powershell", "-Command",
"Get-ADDomainController -Filter * | Select-Object Name, IPv4Address | ConvertTo-Json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
dcs = []
try:
data = json.loads(result.stdout) if result.stdout else []
if isinstance(data, dict):
data = [data]
for dc in data:
dcs.append({
"name": dc.get("Name", ""),
"ip": dc.get("IPv4Address", ""),
"machine_account": dc.get("Name", "") + "$",
})
except json.JSONDecodeError:
pass
return dcs
def query_event_4662(evtx_path=None, max_events=5000):
"""Query Windows Event ID 4662 for directory service access events."""
events = []
if evtx_path:
cmd = ["wevtutil", "qe", evtx_path, "/lf:true",
"/q:*[System[EventID=4662]]", "/f:xml", f"/c:{max_events}"]
else:
cmd = ["wevtutil", "qe", "Security",
"/q:*[System[EventID=4662]]", "/f:xml", f"/c:{max_events}"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
for event_xml in re.findall(r"<Event.*?</Event>", result.stdout, re.DOTALL):
try:
root = ET.fromstring(event_xml)
ns = {"s": "http://schemas.microsoft.com/win/2004/08/events/event"}
data = {}
for el in root.findall(".//s:Data", ns):
data[el.get("Name", "")] = el.text or ""
time_created = root.find(".//s:TimeCreated", ns)
timestamp = time_created.get("SystemTime", "") if time_created is not None else ""
events.append({
"timestamp": timestamp,
"computer": root.findtext(".//s:Computer", "", ns),
"subject_user_name": data.get("SubjectUserName", ""),
"subject_domain_name": data.get("SubjectDomainName", ""),
"subject_logon_id": data.get("SubjectLogonId", ""),
"object_server": data.get("ObjectServer", ""),
"object_type": data.get("ObjectType", ""),
"object_name": data.get("ObjectName", ""),
"access_mask": data.get("AccessMask", ""),
"properties": data.get("Properties", ""),
})
except ET.ParseError:
continue
logger.info("Parsed %d Event 4662 entries", len(events))
return events
def filter_replication_events(events):
"""Filter events for DS-Replication GUID access."""
replication_events = []
for event in events:
properties = event.get("properties", "").lower()
access_mask = event.get("access_mask", "")
for guid, name in REPLICATION_GUIDS.items():
if guid.lower() in properties and access_mask == DCSYNC_ACCESS_MASK:
replication_events.append({
**event,
"replication_right": name,
"guid": guid,
})
return replication_events
def identify_dcsync_suspects(replication_events, dc_accounts):
"""Identify non-DC accounts performing replication requests."""
dc_names = set(dc.get("machine_account", "").lower() for dc in dc_accounts)
dc_names.update(dc.get("name", "").lower() + "$" for dc in dc_accounts)
known_legitimate = {"azureadconnect", "sccm", "adconnect", "microsoftdirectorysync"}
suspects = []
legitimate = []
for event in replication_events:
account = event["subject_user_name"].lower()
domain = event["subject_domain_name"]
if account in dc_names:
legitimate.append(event)
continue
if account.endswith("$") and account in dc_names:
legitimate.append(event)
continue
if any(known in account for known in known_legitimate):
legitimate.append(event)
continue
event["severity"] = "critical"
event["mitre_technique"] = "T1003.006"
event["indicator"] = "Non-DC account performing directory replication"
suspects.append(event)
return suspects, legitimate
def analyze_suspect_patterns(suspects):
"""Analyze patterns in suspected DCSync activity."""
by_account = defaultdict(lambda: {"count": 0, "computers": set(), "guids": set(), "timestamps": []})
for event in suspects:
account = f"{event['subject_domain_name']}\\{event['subject_user_name']}"
by_account[account]["count"] += 1
by_account[account]["computers"].add(event["computer"])
by_account[account]["guids"].add(event.get("replication_right", ""))
by_account[account]["timestamps"].append(event["timestamp"])
patterns = []
for account, data in by_account.items():
has_both = "DS-Replication-Get-Changes" in data["guids"] and "DS-Replication-Get-Changes-All" in data["guids"]
patterns.append({
"account": account,
"replication_requests": data["count"],
"source_computers": list(data["computers"]),
"replication_rights": list(data["guids"]),
"has_full_dcsync_rights": has_both,
"severity": "critical" if has_both else "high",
"first_seen": min(data["timestamps"]) if data["timestamps"] else "",
"last_seen": max(data["timestamps"]) if data["timestamps"] else "",
})
return sorted(patterns, key=lambda x: x["replication_requests"], reverse=True)
def check_replication_acls():
"""Check which accounts have replication rights on the domain object."""
cmd = ["powershell", "-Command",
"(Get-Acl 'AD:\\DC=domain,DC=local').Access | "
"Where-Object {$_.ObjectType -eq '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2' -or "
"$_.ObjectType -eq '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2'} | "
"Select-Object IdentityReference, ActiveDirectoryRights | ConvertTo-Json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
try:
acls = json.loads(result.stdout) if result.stdout else []
if isinstance(acls, dict):
acls = [acls]
return [{"identity": a.get("IdentityReference", ""), "rights": a.get("ActiveDirectoryRights", "")} for a in acls]
except json.JSONDecodeError:
return []
def generate_report(events, replication_events, suspects, legitimate, patterns, acls):
"""Generate DCSync hunt report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"hunt_type": "DCSync Detection (T1003.006)",
"events_analyzed": len(events),
"replication_events": len(replication_events),
"legitimate_replication": len(legitimate),
"suspicious_replication": len(suspects),
"severity": "critical" if suspects else "clear",
"suspect_patterns": patterns,
"accounts_with_replication_rights": acls,
"suspicious_events_detail": suspects[:20],
"recommendations": [
"Disable compromised accounts immediately",
"Reset krbtgt password twice (with 12-hour interval)",
"Audit all accounts with DS-Replication-Get-Changes rights",
"Investigate source hosts for additional compromise indicators",
"Review lateral movement from suspect accounts",
] if suspects else ["No DCSync activity detected - continue monitoring"],
}
return report
def main():
parser = argparse.ArgumentParser(description="DCSync Attack Detection Agent")
parser.add_argument("--evtx", help="Path to exported Security .evtx file")
parser.add_argument("--max-events", type=int, default=5000, help="Max events to parse (default: 5000)")
parser.add_argument("--skip-acl-check", action="store_true", help="Skip replication ACL enumeration")
parser.add_argument("--known-dcs", help="JSON file with known DC hostnames")
parser.add_argument("--output", default="dcsync_hunt_report.json")
args = parser.parse_args()
dc_accounts = get_domain_controllers()
if args.known_dcs:
with open(args.known_dcs) as f:
extra_dcs = json.load(f)
dc_accounts.extend(extra_dcs)
logger.info("Known DCs: %d", len(dc_accounts))
events = query_event_4662(args.evtx, args.max_events)
replication_events = filter_replication_events(events)
suspects, legitimate = identify_dcsync_suspects(replication_events, dc_accounts)
patterns = analyze_suspect_patterns(suspects)
acls = []
if not args.skip_acl_check:
acls = check_replication_acls()
report = generate_report(events, replication_events, suspects, legitimate, patterns, acls)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
if suspects:
logger.warning("ALERT: %d suspected DCSync events from %d accounts",
len(suspects), len(patterns))
else:
logger.info("No DCSync suspects found (%d legitimate replication events)", len(legitimate))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()
Keep exploring