Enumerating Machines
Finding AD-Joined Hosts
The first step you can do is use simple tools to enumerate the internal network, identifying hosts with SMB ports open, as this is the main service used for communication between AD-joined machines and servers. There are 3 main alternatives for that:
nmap
Do a simple port scan across the entire network to find hosts with port 139 and 445 open:
nmap -p139,445 -Pn <IP_RANGE> -oG smb.txt --open && cat smb.txt | grep Up | cut -d ' ' -f2 > smbHosts.txt
Demo on GOAD Lab
nmap -p139,445 -Pn 192.168.56.0/24 -oG smb.txt --open && cat smb.txt | grep Up | cut -d ' ' -f2 > smbHosts.txt
Useful both to enumerate the machines and also identify their hostnames within the domain (which could point to their role):
nxc smb <IP_RANGE>
Demo on GOAD Lab
nxc smb 192.168.56.0/24
responder
In this case, this is a passive enumeration method. We can use responder in Analyze mode to listen to traffic sent in broadcast within the internal network. Active Directory environments usually send a bunch of traffic in broadcast to resolve names, and with those communications you can gather details about the machines.
sudo responder -I <NETWORK_INTERFACE> -A
# Example: sudo responder -I eth0 -A
Demo on GOAD Lab
sudo responder -I eth1 -A
cat /usr/share/responder/logs/Responder-Session.log | grep 192.168.56
cat /usr/share/responder/logs/Responder-Session.log | grep 192.168.56 | grep hostname
Enumerating Available Services
With the list of hosts in hand, leave a network scan running on all of them, so you have a list of available services afterwards.
nmap -T4 -p- -Pn -v -iL smbHosts.txt
It’s also useful to enumerate which of them have the WinRM and RDP services available:
# WinRM
nmap -p5985 -Pn <IP_RANGE> -oG winrm.nmap --open && cat winrm.nmap | grep Up | cut -d ' ' -f2 > winrmHosts.txt
# RDP
nmap -p3389 -Pn <IP_RANGE> -oG rdp.nmap --open && cat rdp.nmap | grep Up | cut -d ' ' -f2 > rdpHosts.txt
Demo on GOAD Lab
nmap -p5985 -Pn -iL smbHosts.txt -oG winrm.nmap --open && cat winrm.nmap | grep Up | cut -d ' ' -f2 > winrmHosts.txt
nmap -p3389 -Pn -iL smbHosts.txt -oG rdp.nmap --open && cat rdp.nmap | grep Up | cut -d ' ' -f2 > rdpHosts.txt
Finding the Domain Controller (DC)
For our luck, usually the server hostname will be intuitive and just have DC in it, buuut there are other ways we can check.
A good one is by consulting default DNS srv
entries in the domain. To query that, we first have to identify the DNS servers:
sudo nmap -sU -p53 -Pn -iL smbHosts.txt --open
If nmap is not giving good results, we can just try to do DNS queries for all IPs we found for the domain and see which of them work… Here is a quick bash script I wrote to do so, just replace the DOMAIN_FQDN in it and you’re good to go:
for ip in $(cat smbHosts.txt);do
results=$(nslookup -retry=0 -type=srv _ldap._tcp.dc._msdcs.<DOMAIN_FQDN> $ip | grep -Ev "timed out|no servers")
if [ ! -z "${results}" ]; then
echo "Valid DNS Server found on $ip"
fi
done
After finding the DNS Server(s), we can go ahead and do the query in one of them to check who is the DC for the domain:
nslookup -type=srv _ldap._tcp.dc._msdcs.<DOMAIN> <DNS_SERVER_IP>
# Example: nslookup -type=srv _ldap._tcp.dc._msdcs.sevenkingdoms.local 192.168.56.10
And finally, just do a simple DNS query to resolve that hostname, if you don’t know the IP for it:
nslookup <HOSTNAME> <DNS_SERVER_IP>
# Example: nslookup kingslanding.sevenkingdoms.local 192.168.56.10
Demo on GOAD Lab
for ip in $(cat smbHosts.txt);do results=$(nslookup -retry=0 -type=srv _ldap._tcp.dc._msdcs.sevenkingdoms.local $ip | grep -Ev "timed out|no servers"); if [ ! -z "${results}" ]; then echo "Valid DNS Server found on $ip" ;fi;done
nslookup -type=srv _ldap._tcp.dc._msdcs.sevenkingdoms.local 192.168.56.10
nslookup kingslanding.sevenkingdoms.local 192.168.56.10
Setting up DNS and Resolution
Now that we now the DC and probably the hostnames of most of the machines, we can setup our local DNS hostnames and resolution. This is specially important to avoid troubleshooting steps in a lot of the tools we use. However, as setting up the DC as our main DNS can also cause some resolution troubles for us in some cases, this is something we’ll have to test in each context and, if necessary, only enable it when we need it.
To configure the hostnames to resolve to their corresponding IP addresses, we need to edit our /etc/hosts
file, inserting first the IP of the machine and after the name we want to be resolved to that (FQDN machine domain name). Multiple names can be specified to each one, and usually when inserting the DC we’ll specify both the machine name and also the domain:
# Example of entry in /etc/hosts
192.168.56.10 kingslanding.sevenkingdoms.local sevenkingdoms.local
To specify the DC or DNS server as the primary DNS resolver for our machine, we need to add its IP to the top of /etc/resolv.conf
:
# Example of entry in /etc/resolv.conf
nameserver 192.168.56.10
nameserver 1.1.1.1
Enumerating Users
Testing Default Users
SMB
There are 2 default SMB accesses you should always test in AD environments: Null sessions and Guest users. The first one is simply trying to access with a null user and null password:
nxc smb smbHosts.txt -u '' -p ''
And the second is with the username Guest
and no password again:
nxc smb smbHosts.txt -u 'Guest' -p ''
If any of these work you’ll probably be able to enumerate the rest of the users through SMB:
# Null Session
nxc smb <IP> -u '' -p '' --users
# Guest User
nxc smb <IP> -u '' -p '' --rid-brute
Demo on GOAD Lab
nxc smb smbHosts.txt -u '' -p ''
nxc smb 192.168.56.11 -u '' -p '' --users
RPC
Note that this could also happen with RPC, so test it with this protocol too:
# Null Session
rpcclient -U "" -N <IP>
# Guest User
rpcclient -U "Guest" <IP>
If any of these work, you can enumerate the rest of the users by using the enumdomusers
command inside the tool.
Demo on GOAD Lab
rpcclient -U "" -N 192.168.56.11
rpcclient $> enumdomusers
LDAP
Null users are also a thing in the LDAP protocol, but here it’s called a Null Bind, and it’s usually only available in the DC or very specific servers. In this example, the domain FQDN is sevenkingdoms.local
:
ldapsearch -H ldap://<IP>/ -x -s base -b 'DC=sevenkingdoms,DC=local' "(objectClass=*)" "*" +
Bruteforcing
A more intrusive, but very efficient approach, is through bruteforcing, using the Kerberos protocol. This works because Kerberos returns different messages when sending TGT requests with no pre-authentication for a user that is valid in comparison with when it’s invalid. In the first case, the KDC prompts for pre-authentication, but in the second, it responds with a PRINCIPAL_UNKNOWN
error.
Because it doesn’t go through the authentication, it’s even stealthier than normal bruteforcing, and does not block accounts. The only thing it generates is a Windows event ID 4768 if Kerberos logging is enabled, which relates to when a TGT is requested.
In order to do that, I recommend trying kerbrute and the wordlists available in the statistically-likely-usernames repository, by InsideTrust. This repo contains very good wordlists that follow different username conventions, for example, the john.smith.txt
wordlist follows the convention user.name
.
kerbrute userenum -d <DOMAIN> --dc <DC_IP> <USER_WORDLIST>.txt -o <OUT_FILE>
Demo on GOAD Lab
./kerbrute userenum -d sevenkingdoms.local --dc 192.168.56.10 ./got_characters.lst -o brute_users.out
Hunting for Valid Accounts with Password Spraying
If you manage to find valid usernames with the bruteforce process, you can try out password spraying with some possible passwords being used. When doing that, be very careful not to exceed the failed authentication limit and block any accounts (I know we can’t know the precise limit yet, but just guess it’s low). Some tips for the process:
Try using the username as password:
# Sprays each user with his username as password (1 try each) nxc smb <DC_IP> -u validUsers.txt -p validUsers.txt --no-bruteforce --continue-on-success
If you already discovered some password in another component of that company, or through leaked credentials for example, or just had an idea that they might use some kind of pattern for passwords, try that too:
# Sprays each user with a specific password nxc smb <DC_IP> -u validUsers.txt -p 'Company@2024!' --continue-on-success
Demo on GOAD Lab
nxc smb 192.168.56.10 -u validUsers.txt -p 'Heartsbane' --continue-on-success
nxc smb 192.168.56.10 -u validUsers.txt -p validUsers.txt --no-bruteforce --continue-on-success
Going forwards
From this point on we have some options to go to:
- First, we can try carrying out Unauthenticated Attacks. Some of them will require us to have a list of valid usernames, but there are options for when we haven’t been able to enumerate that too;
- Buuut, if we found a valid AD account (through null sessions, guest users, password spraying…) we can attempt Authenticated Enumeration aaand Authenticated Attacks too!