Welcome to another Hack the Box walkthrough. In this blog post, I have demonstrated how I owned the Conversor machine on Hack the Box. Hack The Box is a cybersecurity platform that helps you bridge knowledge gaps and prepares you for cyber security jobs.
About the Machine
Conversor is an Easy Linux machine centered around weak credential management and a privilege-escalation vector abusing a Python import-hijacking vulnerability in needrestart v3.7 (CVE-2024-48990). Initial access is obtained by cracking a recovered MD5 password hash for the fismathack user. Once logged in, sudo -l reveals a dangerous configuration: the user may execute /usr/sbin/needrestart as root without a password.
Running needrestart in verbose mode exposes that multiple Python interpreters are invoked during system checks, some originating from writable paths under /var/www. Because version 3.7 of needrestart includes Python-based interpreters in its plugin chain and does not sanitize PYTHONPATH, it becomes susceptible to import precedence attacks where a malicious package shadows core modules.
By compiling a custom __init__.so shared object using a constructor function and hosting it over HTTP, the attacker forces needrestart to import attacker-controlled code during execution. The payload creates a SUID-root shell at /tmp/poc, effectively escalating privileges. Executing a helper script triggers the import chain and results in full root compromise, enabling retrieval of the root flag.
The first step in owning the Conversor machine like I have always done in my previous writeups is to connect my Kali Linux terminal with Hack the Box server. To establish this connection, I ran the following command in the terminal:
Once the connection between my Kali Linux terminal and Hack the Box server has been established, I started the Conversor machine and I was assigned an IP address (10.10.11.92)
After been assigned the IP Address 10.10.11.92, I decided to map the target machine IP address to the domain name. This way, I could access the service by name instead of by IP address by running:
I added 10.10.11.92 conversor.htb to the /etc/hosts file and performed reconnaissance using Nmap to find all the open port and services associated with the target machine.
Service Enumeration
I began the initial enumeration phase with an aggressive Nmap scan to identify open ports and running services on the target:
The scan revealed two open TCP ports - 22 (SSH) and 80 (HTTP):
The SSH service is running OpenSSH 8.9p1 on an Ubuntu system, which indicates that this is likely a modern and patched instance while the web service on port 80 is powered by Apache 2.4.52, also on Ubuntu. The page title was identified as “Login,” and the requested resource /login suggests the presence of an authentication interface, possibly a web application that could hold our entry point.
Directory enumeration
I ran a quick directory enumeration against the webroot to see what hidden endpoints the app exposes:
Gobuster finished and returned a short but useful list of discovered paths.
What the scan shows
/login, /register, /logout), an informational page (/about), a javascript directory, a convert endpoint that refuses GETs, and a protected server-status endpoint. Several entries redirect unauthenticated access back to /login, which suggests the app enforces auth for some pages.First interaction - landing on the webapp
After the directory brute force revealed a handful of endpoints, I pointed my browser at the target IP (http://10.10.11.92). The server immediately redirected me to the application’s friendly hostname and login page:
The page loads a modern-looking authentication UI with two input fields: Username and Password - and a Login button. There’s also a Register link available for creating a new user.
After registering and signing in, I was redirected to the app’s main interface - a simple but telling upload form titled Conversor. The page allow users to upload two files: an XML File (Nmap report) and an XSLT File (the stylesheet used to transform the XML into a prettier HTML). There’s also a “Your Uploaded Files” area and a prominent Convert button.
Crucially, the page includes a download link labeled Download Template that points to:
I clicked it and downloaded nmap.xslt. That single file is a strong reconnaissance win: it reveals exactly how the server expects to transform uploaded Nmap XML, and therefore gives clues about the XSLT processor, template structure, and any helper functions or assumptions the app makes.
About page → source code discovery
After poking around the authenticated UI I noticed the About page, which at first looked like a standard “meet the team” blurb. Near the bottom I noticed a Download Source Code button that pointed to:
I downloaded and extracted that archive immediately. Instead of reverse-engineering behavior from network traffic and guesses, you get the developer’s own implementation and comments. The archive can reveal exactly how uploads and conversions are handled, where files are stored, which third-party libraries and XSLT processors are used, how /convert is implemented, and (often) hardcoded paths, config values, and developer notes.
Source archive contents
I downloaded and extracted the application source (source_code.tar.gz) to get a look at the source code by running:
The archive contains the server entry point (app.py) and a WSGI wrapper (app.wsgi), deployment/setup docs (install.md), an instance/ folder with a SQLite database (instance/users.db), helper scripts/, static assets (including the previously downloaded static/nmap.xslt and static/style.css), the Jinja templates that power the UI (templates/*.html), and an uploads directory.
Why each file/folder is important
app.py- server logic / routes. This is the single most important file to read: it tells you how/convert,/login,/registerare implemented, where uploads are written, and what sanitisation (if any) is performed.app.wsgi- deployment entry; shows how the app is started and with which environment (useful for runtimes/paths).install.md- setup instructions; may reveal required packages, used XSLT engine, or environment variables and default credentials.instance/users.db- SQLite database containing user records (likely usernames + password hashes). You can dump this locally to enumerate accounts or test login credentials.scripts/- could contain helper scripts/runbooks/cron jobs. Check for scheduled tasks that may move or execute uploaded files.static/nmap.xslt- canonical XSLT template (we already downloaded this). Great base to craft malicious XSLT because it shows expected structure and namespaces.templates/*.html(login.html,register.html,result.html, etc.) - reveal form field names, multipart field names, and exact parameter names the server expects (so you can craftcurl/Burp requests exactly).uploads/- filesystem location for uploaded files. If the application saves uploads here with predictable names or serves them, it can be abused to read arbitrary files or achieve remote inclusion.
Database reconnaissance
I inspect the files in extracted achieve including install.md, README, and cron entries and also instance/ or data/ directories that could possibly hold an application DB (often SQLite for labs) and found users.db.
I pulled the bundled SQLite database at instance/users.db to see whether any credentials or file metadata were available:
A schema dump revealed two relevant tables: users(id, username, password) and files(id, user_id, filename) (plus the sqlite_sequence autoincrement table). The password column is stored as TEXT, so the app may use plaintext or stringified hashes. My initial inspection showed no obvious rows, which is common for CTF labs that ship an empty DB;
I therefore queried the DB with sqlite3 to confirm and then registered a user through the app to observe how records are inserted (and how uploaded files are referenced):
Knowing the files schema let me link database entries to the app’s uploads directory and craft tests to retrieve or predict uploaded file paths - a useful step toward abusing the XSLT upload/transform flow.
Deployment notes & security implications
I checked the project’s install.md to understand how the app is intended to be deployed and hosted:
Attack surface
- The app is a simple Flask application and can be run with
python3 app.pyor deployed under Apache viaapp.wsgi. That tells us the runtime (Python/Flask) and possible process user maps (Apache likely runs aswww-data), which is crucial when thinking about file permissions and exploit impact. - The crontab line is the most interesting security detail: it runs every minute as the
www-datauser and executes every*.pyfile inside/var/www/conversor.htb/scripts/. In practice, that means:
- Any Python file present under that scripts directory will be executed regularly by the web server user — a powerful primitive if an attacker can place a file there.
- If uploads, temp file moves, or other vulnerabilities allow an attacker to write to
/var/www/conversor.htb/scripts/(or a symlink into it), they could achieve code execution aswww-datawithout needing to exploit the webserver directly.
- The comment about deleting files older than 60 minutes explains why uploads may disappear: the app uses scheduled cleanup, so any payloads or output may be ephemeral and must be observed quickly.
- Running via Apache + WSGI vs plain
python3 app.pyaffects how files are served and which user executes processes. Apache/WGSI typically runs aswww-data, so the crontab entry confirms that service context.
Nmap sweep - saving the scan as XML
I ran an aggressive Nmap scan and saved the output to nmap.xml (this file will be useful later for the Conversor upload/transform step):
- Saving the scan as
nmap.xmlis deliberate: Conversor’s UI accepts an Nmap XML file and an XSLT stylesheet to render a prettier report. That means the exact scan I just ran can be uploaded to the target app and transformed using the providednmap.xslt(or a modified XSLT) - useful both for reporting and for testing XSLT-based payloads. - The small number of services makes our attack surface focused and predictable: the web app on port 80 is the obvious target (we already found upload/convert functionality and the XSLT vector). SSH is present but likely requires credentials (we’ll try credentials discovered via the app or DB first).
The nmap.xml file was uploaded alongside with the nmap.xslt stylesheet to generate a polished, human-readable Nmap scan report.
After uploading the files, I clicked on the link and I got a readable Nmap scan report:
Crafting a malicious XSLT to obtain a shell as www-data
I created a malicious XSLT (shell.xslt) that abuses the XSLT processor’s EXSLT “common” extension to write a Python file into the target’s scripts/ directory:
The goal is to drop shell.py under /var/www/conversor.htb/scripts/ so the crontab entry (which runs every minute as www-data) will execute it and give me a callback.
Before triggering the XSLT payload, I needed two things ready on my machine:
- A Python HTTP server to host the shell.sh payload the target would download.
- A netcat listener to catch the reverse shell once the script executed.
I started by spinning up a quick web server on port 8000:
This would allow the vulnerable server to fetch and execute my shell script. Moments after the XSLT payload ran, I saw incoming requests from the target machine, confirming code execution. With the payload successfully retrieved, I set up a listener on port 4444 to await the reverse shell:
A few seconds later, I got the reverse shell - proof that the injected Python code had executed as intended.
I was now inside the machine as www-data. I began enumerating the web application:
Inside the project folder, the key components of the Flask app were visible:
The empty-looking users.db wasn’t the real target. Flask applications often store their active database in the instance directory, so I checked there next:
This was the actual database. I opened it using sqlite3, which was available on the system:
Dumping the users table provided stored credentials:
With the database dump in hand, I focused first on the fismathack user. The stored password was clearly an MD5 hash:
Since MD5 is fast and unsalted, it’s trivial to crack using online rainbow-table services. I headed over to CrackStation.net, pasted in the hash, solved the CAPTCHA, and let the Free Hash Password Cracker engine do the rest.
Within seconds, CrackStation returned an exact match. The plaintext password was revealed to be:
This gave me valid credentials belonging to one of the developers, a strong indication that these might be reused elsewhere on the system. With a freshly cracked password in hand, the next step was to try leveraging it for privilege escalation or SSH access.
Privilege pivot - SSHing in as fismathack and capturing user.txt
With the MD5 hash cracked and Keepmesafeandwarm recovered as the plaintext password for the fismathack account, the next logical step was to see whether the user had reused this credential for system access. I attempted SSH login using the cracked password - and it worked:
The server immediately dropped me into a full shell on an Ubuntu 22.04 host, confirming that the user reused their application password for SSH. The very first thing I checked was the presence of the user flag, and sure enough, it was sitting right there:
Hurray, I got the user flag!!!
After capturing the user flag, I moved on to enumerating potential privilege escalation vectors. The first and most obvious check was sudo permissions, to see whether the compromised account had access to any privileged binaries:
The output was immediately interesting. The account fismathack was allowed to run a system utility without providing a password:
This meant the user could execute needrestart - a tool typically used to check which services require restarting after updates with full root privileges.
With sudo access to needrestart confirmed, the next step was to investigate whether this binary had any known privilege-escalation flaws. A quick Google search led me straight to CVE-2024-48990, a recently disclosed vulnerability affecting needrestart v3.7 - the exact version installed on the box.
The issue stems from the way needrestart relies on Python modules during execution. When run with elevated privileges, the tool trusts Python’s import mechanism without properly sanitizing the module search path. Since Python resolves imports based on sys.path and environment variables like PYTHONPATH, an attacker with write access to any directory included in this lookup chain can plant a malicious module that gets imported when needrestart runs.
And because I could execute needrestart using:
without a password, this meant any Python code the binary imported would execute as root.
The exploitability lined up perfectly:
- The installed version (v3.7) was vulnerable.
sudoallowed me to run it with full privileges and no password prompt.- The filesystem provided writable locations that Python would naturally search during module resolution.
In other words, I now had a clear, weaponizable path: craft a malicious Python package with the right name, place it somewhere needrestart would import from, and let the binary hand me a root shell on a silver platter.
To better understand how needrestart behaved under elevated privileges, I executed it manually using verbose mode:
The debug output immediately confirmed several important details. First, the tool was indeed running as root, and more importantly, it revealed the exact interpreter modules that needrestart attempted to analyze. Each Python interpreter instance was logged with a unique ID, and several of them pointed to:
This was interesting for two reasons.
-
The script resided in a web-accessible directory, meaning the attacker (me) could likely write files there.
-
needrestartrepeatedly recognized these Python scripts and tried to inspect them, but each time they were marked as blacklisted, yet still fully processed through Python’s import resolution logic.
The output also confirmed that the system was running needrestart v3.7, the exact version known to be vulnerable (CVE-2024-48990). Even more revealing was this line:
This showed that needrestart invoked Python in multiple ways, some with script files and some through direct interpreter commands. That meant Python’s import mechanism would be triggered several times during execution - perfect for slipping in a malicious module.
Post Exploitation - Leveraging CVE-2024-48990
During enumeration, I discovered that the system was vulnerable to CVE-2024-48990, a flaw affecting Python’s importlib behavior when handling specially crafted shared objects. With the right payload, the Python interpreter could be tricked into loading a malicious library, giving us a path to arbitrary code execution under elevated privileges.
To weaponize this, I cloned a public proof-of-concept repository:
Inside the cloned directory, the PoC included a runner.sh script designed to orchestrate the exploit. Before executing anything, I modified the script to point to my attacker IP, where I would host the malicious shared object:
N
To prepare the actual exploit payload, I wrote a shared library in C (lib.c). The constructor function runs immediately upon import, giving me a perfect hook point. The logic is simple: if the code executes with effective UID 0, it plants a root shell in /tmp/poc, marks it SUID, and even ensures sudoers allows passwordless execution of that binary:
After compiling it into the malicious shared object:
I edited the runner.sh file and changed the IP address to mine by checking using ifconfig. The content of the runner.sh is:
I spun up a Python HTTP server on my attack machine and hosted the new payload.
Back on the target, from within /tmp, I downloaded my modified exploit runner:
At this point, everything was staged: the crafted malicious shared library, the auto-triggering Python script, and the shell planting payload. Running runner.sh would cause Python’s import mechanism to load my __init__.so file - executing arbitrary code under elevated privileges and handing me the final route to root.
Executing the Exploit and Gaining Root
With the malicious payload prepared and hosted on my attacker machine, the next step was to deliver and execute the exploit on the target. I moved into /tmp, the only directory writable by the unprivileged fismathack user, and pulled down my modified exploit runner:
Once the script landed on the box, I verified its presence and marked it executable. The /tmp directory was filled with the usual systemd temporary files, but right in the middle of it sat my freshly downloaded runner.sh - exactly where I needed it.
Running the script kicked off the full chain:
- it created the malicious Python import path,
- downloaded my engineered
__init__.sopayload, - launched the Python trigger loop,
- and waited for the import to execute long enough for the shared object’s constructor to fire.
Within seconds, the script printed the line every attacker hopes to see:
“Got shell!, delete traces in /tmp/poc, /tmp/malicious”
This confirmed that the payload had executed successfully and that the SUID-root shell (/tmp/poc) had been created. Dropping into the shell and running id immediately validated the escalation - my privileges had shifted from the restricted fismathack account straight to uid=0, gid=0, full root access.
With full control of the system, I navigated into /root and retrieved the root flag:
Hurray! I got the root flag. With that the machine was officially pwned.
If you enjoy reading my walkthrough, do not forget to like, comment, and subscribe to my YouTube channel and also connect with me on LinkedIn. Also, don't forget to turn on post notification on my YouTube channel and Medium to get notification as soon as I write.
Subscribe to my YouTube channel and Follow me on: LinkedIn | Medium | Twitter | Boltech Twitter | Buy Me a Coffee
Keywords:
Conversor Hack the Box Writeup
Conversor Hack the Box Machine Writeup
Conversor Hack the Box Walkthrough
Conversor Hack the Box Machine Walkthrough
Conversor HTB Writeup
Conversor HTB Walkthrough
Conversor HTB Machine Walkthrough
Conversor HTB Machine Writeup
Conversor Hack the Box HTB Machine Walkthrough Writeup
Conversor HTB Solution
Conversor Hack the Box Solution
Conversor Hack the Box Complete Walkthrough
Conversor Hack the Box Complete Writeup
Conversor Hack the Box Complete Solution
Conversor htb write up
HackTheBox Conversor Writeup
HTB Conversor Writeup.
HackTheBox Conversor Writeup
HackTheBox — Conversor (Writeup)
HTB Writeups - Conversor
HackTheBox Conversor Writeup
Conversor | HTB Writeup | Linux
HTB — Conversor Writeup
Owned Conversor from Hack The Box
Hack The Box - HTB Conversor Writeup
HTB Conversor Detailed Writeup English
HackTheBox | Conversor HackTheBox · HackTheBox | Conversor
Conversor HTB Writeup | HacktheBox | Season 9
HTB Writeup – Conversor
Write-ups Category Posts | Hack The Box Blog
Conversor - Hack the Box - Walkthrough
Conversor - HTB - Walkthrough
Conversor - Hack the Box - Writeup
Conversor - Hack the Box - Writeup





































0 Comments