Preface
You should know the drill by now. If not, you probably haven't read the previous blog post in the series, so be sure to check it out as well.
In this blog post, I will share my process from a real engagement I did recently. This experience will serve as an opportunity to share some tips, techniques, and methodologies that may be of help to you.
Setting The Scene
The Goal: It’s pretty straight forward, gain Domain Admin privileges and document any findings along the way.
The Starting Point: VPN into the internal network of the organization, domain user credentials (Uses a randomly generated password, so no generic password to spray this time...)
Type of Engagement: Coverage-based, non-stealth, internal network PT.
TLDR:
- Don't be lazy, read on! Trust me, this will be a good one.
Note: During this engagement, I reached Domain Admin privileges in 6 different ways. In this blog post, however, I will walk you through one of the most interesting ways of the 6 rather than going through all of them. This is the third method, chronologically speaking, I used to reach the DA target. Therefore, the walkthrough may feel a bit contrived since I'm skipping some parts not related to this particular path to DA.
Act 1: Finding Our Bearings
When your starting point for an engagement is a VPN, it can complicate things you might normally take for granted. For example, DNS – since you use your own workstation as opposed to an already domain-joined workstation provided by the organization, you need to figure out the DNS suffix for the domain to locate the different assets of the domain (domain controllers for example).
Insight: Organizations use DNS Suffix Search List to have clients "Suffix" DNS queries with multiple domains. This can reveal the domain names used by the organization. To find the DNS Suffix Search List, run the command ipconfig /all. For more information, visit:
https://blogs.msmvps.com/acefekay/2011/02/12/configuring-dns-search-suffixes/
If you are lucky, your VPN will set up the DNS Suffix Search List automatically. If you are like me, however, then you need to find it yourself…
Wireshark can sometimes help here. At the beginning of this engagement, I started listening on my VPN's network adapter and found the domain name pretty quickly in one of the packets. (Unfortunately I didn't save a screen capture, so you’ll just have to use your imagination.)
Now that we have possible domain names, we can query the DNS set by the VPN for the DC. A simple nslookup domain.local can provide us with the IP addresses of the domain controllers. However, if we want more detailed information we can use this query:
nslookup -q=srv _ldap._tcp.dc._msdcs.domain.local
With the IP addresses of the domain controllers, we can connect to them using our provided domain user credentials. We can also start enumerating the domain, find out our privileges, discover where can we connect, find interesting targets, etc. Here, we can use our trustee SharpHound, especially in cases where we use our own machine and have no AV/EDR to contend with. But why make it easy for ourselves? Where's the fun in that? Instead of SharpHound, I've chosen to use ADExplorer to create a snapshot of the domain and load it into BloodHound using ADExplorerSnapshot.py tool. This method can also be a bit stealthier, in some environments, than running SharpHound.
Another interesting source of information is ADIDNS. Dirk-Jan Mollema wrote about ADIDNS on his blog and I recommend giving it a read. Using his adidnsdump tool, we can enumerate all the records in a DNS zone. We start by enumerating the available zones using the --print-zones flag:
Then, we dump all the records of the DNS zone we care about, which in this case is SEVENKINGDOMS.LOCAL (our lab environment):
Now that we have a list of all DNS records, we can extrapolate the different IP ranges used in the domain. With our list of targets in the bag, let's get to work!
Act 2: LDAP Relay
One of my "go-to" techniques lately has been the LDAP Relay (A more accurate name for this technique is NTLM relay to LDAP, but the name LDAP Relay stuck better.). Given that LDAP signing is not required in the domain controller (the default settings and also by far the most common configuration) and given you can obtain a non-SMB NetNTLM authentication from a machine account (printerbug FTW), you can relay that non-SMB NetNTLM authentication to the LDAP service on the domain controller and get an LDAP session as that machine account. From there, you can abuse RBCD or Shadow Credentials to take over the machine itself.
I like this technique so much that I even created a tool to streamline the process given you have code execution on the machine itself as a low privilege user (DavRelayUp - go check it out).
One caveat of this technique is that non-SMB NetNTLM authentication – using PrinterBug, PetitPotam or any other method of coercion for that matter – usually results in SMB NetNTLM authentication. You cannot relay this to LDAP due to session signing and MIC. So, what is our alternative? HTTP (WebDAV to be precise). We can coerce WebDAV authentication using a simple trick of adding a port to the UNC path we coerce. This means that instead of coercing authentication for \\attacker\asdf, we will coerce it for \\attacker@80\asdf. However, there are a few prerequisites you must consider for this to work:
- LDAP signing and LDAP channel binding should not be enforced/required in the domain controller.
- The WebClient Service must be running on the coerced machine (which is not always the case).
- If we want to abuse RBCD we also need a machine account under our control or a sacrificial user account (to abuse the U2U technique). In case of Shadow Credentials we can also use the UnPACTheHash trick to get the NTLM hash of the machine account and then create a silver ticket.
- The relay server (the UNC path we coerce authentication to) needs to be considered as an intranet zone for the WebClient to automatically authenticate using NetNTLMv2. This means that using our relay server IP address will not work. If we have a domain-joined machine, or if we are on the same subnet as the victim machine, we can simply use our hostname (not FQDN).
- The coerced machine needs to have network connectivity to our relay server (duh…)
Alright, let’s get back to our story. Using LdapRelayScan script, I confirmed that the domain controllers are not requiring LDAP signing, so that's the first prerequisite down.
I then used CrackMapExec’s webdav module to scan the IP ranges we gathered from ADIDNSDump to target machines that have the WebClient Service already running.
According to the scan, I had a few options, but the one I chose to target was the TS50.
Insight: Terminal servers should be considered high value targets since they usually contain lots of sessions from various users. Sometimes you may even find highly privileged users.
Now that we have our target machine with the WebClient Service already running, we have two of the five prerequisites. For the third prerequisite, we need a machine account. Thankfully, the msds-MachineAccountQuota attribute in the domain is set to the default (10) which allows for machine account creation by any authenticated user.
We can create a new machine account in the domain using Impacket's addcomputer.py script.
For the fourth and fifth prerequisites, we need our relay server to be considered an "intranet zone". We also need to make sure that the target machine has network access to our relay server. Unfortunately, this isn’t the case since we are connected through a VPN – we have access to the internal network, but servers on the internal network do not have access to us. I confirmed this using PrinterBug (vanilla SMB, no WebDAV) – I didn’t receive any traffic from the target machine on my relay server.
We can kill two birds with one stone here by abusing ADIDNS and SSH pivoting. Here’s what I did:
- I created a new server on DigitalOcean (any other cloud provider should work just fine) and made sure port 80 was open and accessible from the Internet.
- In the new server, I changed GatewayPorts to yes in /etc/ssh/sshd_config. This allows us to port forward external ports (i.e. not only 127.0.0.1 but 0.0.0.0) using SSH.
- Using dnstool, I added a new A record to the organization’s ADIDNS that points to my new server’s public IP address. This will ensure that when coercing WebDAV NetNTLM authentication to wuattacker (no FQDN), the ADIDNS is able to resolve it. As we don't use FQDN here, this is considered an intranet zone, thus satisfying the fourth prerequisite.
- To complete the fifth and final prerequisite, I created an SSH Reverse Port Forwarding that would tunnel port 80 from our newly created server on the cloud to my VPN’d attacker machine.
Now, all that remains is to setup ntlmrelayx on my VPN’d attacker machine and coerce WebDAV NetNTLM authentication from our target TS50 to the newly added A record wuattacker. The coerced authentication will be routed through our server on the internet to our VPN’d attacker machine via the SSH tunneling we set up. It will then be relayed to LDAP to add RBCD rights for our machine account on the target server.
Abusing our new RBCD rights is pretty straightforward. Using the credentials of the machine account we previously added, which now has RBCD rights over the TS50, we request a service ticket for the SPN host/ts50.domain.local while impersonating (S4U) a domain user with local admin privileges on TS50 (domain admin should work if you are feeling lazy).
After obtaining the service ticket, we can use it to perform lateral movement to the remote machine using tools like Impacket’s wmiexec.py for example.
We can then perform a SAM dump remotely using Impacket’s secretsdump.py.
Insight: After performing SAM dump, look at the built-in administrator NT hash. If it ends in "089c0" the user has no password (empty password), which probably means it is disabled. If it’s not disabled, always check if the password (NT hash) is being used someplace else.
Act 3: It's a LAPSless world we live in
Noticing that the built-in Administrator has a non-empty password hash (does not end in 089c0) I knew it was active on the server. Factoring in the fact that I couldn’t find any evidence of LAPS usage in the domain, I decided to spray that NT hash across the domain to see if they are using the same built-in administrator password for other servers and workstations as well.
Awesome, we now have admin access to most of the machines in the domain! To bring this one home, all we need is to find a machine with a domain admin user logged on. We can then simply impersonate them (token duplication).
CME to the rescue again, I used the --loggedon-users flag to list the each machine’s sessions and found some with domain admin users logged on.
With the target acquired, we can use CME’s impersonate module, which under the hood uploads and executes impersonate.exe on the remote host, to run commands as the domain admin user. First, we list the available primary tokens on the machine to identify the token ID of the targeted user:
After identifying the user's token ID, we can upload to the remote host a simple batch script using smbclient. This script simply executes net.exe group “domain admins” attacker /add /domain. I chose to go this route due to problems I experienced in the past with nested quotes in the command line arguments. With the makemeadmin.bat script uploaded, I used CME’s impersonate module again to run the script as the domain admin user:
Voilà, mission accomplished 🙂
Insight: Although there are many ways of dumping LSASS and revealing the NT hash of your target user, sometimes it’s simpler (and can be stealthier, although not the way I did it in this case lol) to just duplicate the token from another user’s process.
Detections And Mitigations
Dumping DNS Zone information (ADIDNSDump)
MITRE ATT&CK ID: T1140
Reference URL: https://attack.mitre.org/techniques/T1140/
- Consider hardening the DACL of the various ADIDNS zones to only allow admins to list child objects ("Everyone" group by default). This limits normal users from enumerating all the records in the ADIDNS zone.
New Machine Account Creation
MITRE ATT&CK ID: T1136.002
Reference URL: https://attack.mitre.org/techniques/T1136/002/
- Set the domain's ms-DS-MachineAccountQuota to 0, instead of the default value of 10.
- While not enough on its own, This will at least require the attacker to obtain control of a machine account through some means other than just simply adding one.
- Monitor for computer creation events (Event ID 4741) followed by computer account password reset events (Event ID 4724)
Creation of new A records in ADIDNS
- Consider hardening the DACL of the various ADIDNS zones to only allow admins and machine accounts to create child objects ("Authenticated Users" group by default). This limits normal users from creating new records in the ADIDNS zone.
LDAP Relay and Auth Coercion
MITRE ATT&CK ID: T1187
Reference URL: https://attack.mitre.org/techniques/T1187/
- Configure LDAP signing and LDAP channel binding. Microsoft has provided guidance for enabling LDAP channel binding and LDAP signing.
Resource Based Constrained Delegation (RBCD)
- Monitor for AD object modifications (Event ID 5136 or 4662) where the attribute being modified is msDS-AllowedToActOnBehalfOfOtherIdentity.
- Mark privileged accounts as Account is sensitive and cannot be delegated in Active Directory. This will cause the KDC to report a KDC_ERR_BADOPTPION error when requesting a TGS for the sensitive account through S4U functions.
SAM Dump
MITRE ATT&CK ID: T1003.002
Reference URL: https://attack.mitre.org/techniques/T1003/002/
- Monitor for Event ID 4656 with the object name set to HKLM\SAM, HKLM\SECURITY or HKLM\SYSTEM.
Adding user to Domain Admins group with net.exe
MITRE ATT&CK ID: S0039
Reference URL: https://attack.mitre.org/software/S0039/
- Monitor for the use of net.exe, particularly if you’re seeing it used with group/user/add/del/dom/domain/password/localgroup.
- Monitor for Event ID 4728 “a member was added to a security-enabled global group”.