Hack The Box: Forge

Jump Ahead: EnumUserRootResources


To solve this machine, we begin by enumerating open ports using nmap – finding ports 21, 22, and 80 open. From the web server, we find a Server-Side Request Forgery (SSRF) vulnerability. Using the SSRF vulnerability, we are able to enumerate the web server, and eventually find credentials for an internal FTP server. Using the SSRF vulnerability to connect to the FTP server, we are able to get user.txt and the user’s SSH private key. After using the private key to connect to the machine via SSH, we find we are able to run a python script as root. Exploiting the script, we are able to get a shell on the machine as root, and can read root.txt.


Like all machines, we begin by enumerating open ports using nmap. From our scans, we find ports 21, 22, and 80 filtered or open.

$ sudo nmap -v -p- --min-rate 3000 $RHOST
$ sudo nmap -sV -A -oA enum/nmap/tcp-scripts $RHOST
# Nmap 7.91 scan initiated Sat Sep 11 14:22:34 2021 as: nmap -sV -A -oA enum/nmap/tcp-scripts
Nmap scan report for forge.htb (
Host is up (0.071s latency).
Not shown: 997 closed ports
21/tcp filtered ftp
22/tcp open     ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 4f:78:65:66:29:e4:87:6b:3c:cc:b4:3a:d2:57:20:ac (RSA)
|   256 79:df:3a:f1:fe:87:4a:57:b0:fd:4e:d0:54:c6:28:d9 (ECDSA)
|_  256 b0:58:11:40:6d:8c:bd:c5:72:aa:83:08:c5:51:fb:33 (ED25519)
80/tcp open     http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Gallery
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 53/tcp)
1   155.09 ms
2   155.26 ms forge.htb (

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Sep 11 14:23:04 2021 -- 1 IP address (1 host up) scanned in 29.94 seconds

From the results, we see that port 21 is filtered, which suggests it may be listening, but not responding to our connection attempt. Using tools like gobuster and nikto, we attempt to enumerate the web server, however, we do not find anything that looks particularly interesting. When we attempt to connect to the web server, we are redirected to http://forge.htb. Since this FQDN does not exist, we add it to our /etc/hosts file, and retry accessing the web server. This time, we are able to see what appears to be an image gallery.

From the “Upload an image” page, we are given the option to upload images either from a file, or from a URL.

Next, we try to test the URL uploader for validation checks, and see that the hostname and protocol are both checked. Additionally, we see that the FQDN forge.htb is also blacklisted.

Next, we check if we are able to bypass the protocol and hostname restriction by converting the “localhost” ip to decimal, and try to access port 21 to confirm our suspicion of its use. We are successful, and see that port 21 is listening locally.

Since, we are able to upload from URLs, we see if we are able to get the machine to connect to our machine. Doing so, we see that we do get a connection from the machine, and that it’s using python requests for the connection.

Next, we test the file uploader to see if we are able to upload server code and execute it. As a POC, we try to see if we can get phpinfo() to execute (assuming php is installed with Apache), however, we see that the file is displayed back as an image.

Lastly, we attempt to enumerate possible vhosts using ffuf, and find that admin is a valid vhost. When we add the subdomain to our /etc/hosts file and attempt to access it, we see that it is only accessible locally.

Getting User

Since we know there is an internal admin subdomain, we need to bypass the hostname restriction in order to view it. Since we are able to get the URL image uploader to connect to our machine, we can try to send an HTTP redirect response to the admin site. If we are successful, we may be able to view the contents of the site – which means the image uploader is vulnerable to Server-Side Request Forgery (SSRF). To do this, we start by creating a file that will hold the redirect response.

$ cat redirect
HTTP/1.1 301 Moved Permanently
Date: Sat, 11 Sep 2021 19:20:22 GMT
Server: Apache/2.4.41 (Ubuntu)
Location: http://admin.forge.htb/

Next, we open a netcat listener, and supply the redirect response as its input. When the uploader connects to our machine, it will receive the redirect response from netcat. Lastly, we submit the upload request to connect to our machine on port 80. After the machine connects, we kill the netcat connection to stop the connection from hanging. Once the connection closes, we get a response from the uploader that suggests the redirection was successful.

Once we open the link we are given to view the upload content, we look at it in BurpSuite (or whatever Proxy tool you use). From its content, we see the redirection was successful, and see it’s an admin page that includes a link to http://admin.forge.htb/announcements.

Repeating the same process to see the announcements link, we get credentials to an internal FTP server. This confirms our suspicions as to why the nmap scan for port 21 showed as “filtered”.

Once again, we attempt to use the redirection to connect to the FTP server. Doing so, we see that it’s the user’s /home directory, and in it we see user.txt – which we are able to read. Since our FTP access connects us to the user’s home directory, we can attempt to get the user’s ssh private key. Doing so, we do in fact get the user’s ssh key. We save it to our local machine, set the proper permissions, then connect to the SSH server as the user. If we did not use the SSRF vulnerability to read user.txt, we can formally read it now.

Getting Root

Since we have a shell as user, we start looking for ways that we may escalate our privileges to root. The first thing we try to run is sudo -l. Doing so, we see that we are able to run a python script as root.

Looking at the script, we see that it spawns a local server on a random port. Once we connect to the server, we are able to run a few commands as root after supplying the admin password. Interestingly enough, we see that the pdb module is used in cases where an exception is thrown.

Researching pdb.post_mortem, we find an article that explains pdb, and what can be done with pdb.post_mortem. From it, we see that the ! command allows us the execute python code similarly to python‘s exec() function. Since we can run the script as root, we can abuse this functionality to get a shell on the machine as root. To do so, we run the script as root, connect to the server, and supply the admin password. Since we need to generate an exception to exploit the script, we can supply a string as the command to run since the program is expecting a number. Once we supply a string and trigger an exception, we give the command ! import os;os.system("/bin/bash") to get a shell as root. With that, we can now read root.txt.

Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!