Home Forge - HackTheBox
Post
Cancel

Forge - HackTheBox

Forge is a very nice medium linux box featuring a web service that allows for local and remote file upload (via URL). After the upload, user is given a random URL to access the uploaded file. This URL upload functionality has a filter that attempts to block SSRFs, but the filter is flawed, and could be exploited to reach another subdomain on the server that foreign hosts are not allowed to directly access. The vector for privilege escalation is a python script that drops into a pdb shell when user caused an exception.


Info



Recon


NMAP

# Nmap 7.70 scan initiated Thu Oct 21 20:28:51 2021 as: nmap -sC -sV -oN nmap.txt -v 10.10.11.111
Nmap scan report for forge.htb (10.10.11.111)
Host is up (0.23s latency).
Not shown: 997 closed ports
PORT   STATE    SERVICE VERSION
21/tcp filtered ftp
22/tcp open     ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open     http    Apache httpd 2.4.41
| http-methods: 
|_  Supported Methods: OPTIONS HEAD GET
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Gallery
Service Info: Host: 10.10.11.111; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Oct 21 20:29:42 2021 -- 1 IP address (1 host up) scanned in 51.39 seconds

Web

There is an image upload functionality at /upload that the homepage links to;

Uploading a sample image file saves it to the below location on the server;

Clicking the Upload from url in the page above gave me an input field for a URL. Attempts to load a local file using the file:// protocol failed, saying only HTTP and HTTPs protocols are allowed. Entering a URL to my attack host, I got a callback;

The python-requests user agent in the header of the request makes me suspect this is some kind of Python WSGI web application. Such applications, from my experience, are an absolute pain to gain command execution through arbitrary file uploads due to the special way they handle paths, so I’m going to ignore this.

Bruteforcing the web root with ffuf didn’t yield anything of interest, but bruteforcing for hidden subdomains found one (I had to filter out HTTP redirects as all invalid subdomains result in them);

Going to the link, I was told access is only allowed to localhost;

Judging from the name of the box, this makes me suspect I need to do some request forgery to fool the web app into thinking I am connecting from localhost. Bruteforcing it’s web root found a directory named static, which is similar to the /static page found on forge.htb, except the latter contains an images directory.

My first approach to get around this was to use the image upload functionality on forge.htb/upload. I tried to get web app to send a request to the admin domain, which was blocked;

My next step was using special HTTP headers to fool the web app into thinking the request to http://admin.forge.htb came from a different host. It didn’t work. So I performed a full port scan on the host in hope that I missed something that may be used to access this page, like a proxy service. No new port was discovered.


Foothold


Replacing the subdomain in the URL upload functionality with a random string while keeping the forge.htb hostname resulted in the same URL contains blacklisted address response, which indicate the web app is probably not using it to filter submitted URLs, but rather the hostname. Since hostnames are case-insensitive, I tried to bypass the filter by changing the case of the hostname, and it worked;

The web app does not appear to be validating if the contents fetched from the given URL is infact a valid image data, which allowed me to read the HTML code of the admin.forge.htb domain by curling the returned URL;

Using the same trick to use the file:// protocol to read local files didn’t work. The above HTML code revealed the path /announcements. Using the URL upload trick above, the file was dumped, and was found to contain some very interesting info, including the credential user:heightofsecurity123!;

Attempting to connect to the SSH service using the credential showed the SSH server is likely configured to only allow public key authentication.

The contents of the admin.forge.htb/announcement page said it has a file upload function that can be invoked using HTTP request to /upload with the URL passed in the GET parameter u, so tried to use the file:// protocol via this u parameter. The upload returned a success message, but curling the file showed the protocol was blocked, and listed the protocols that are allowed;

Testing the FTP protocol by giving it an FTP URL to my attack host (ftp://testuser:testpass@<my-IP>/) with test credentials, tcpdump captured an authentication attempt for the target host;

Since NMAP has initially reported an FTP service on the host on port 21 as filtered, I used the credentials of user that was previously obtained to connect to the FTP service running locally on the target. It didn’t work, saying the URL contains a blacklisted address. Using the change of case trick used previously bypassed the filter;

Requesting the generated link with curl, I got the contents of the root directory of the FTP service;

The presence of the user.txt file indicate the FTP root directory is the home directory of a user. And since the SSH service on the host is blocking password authentication, I’m guessing the user will have public key authentication configured. So I crafted a request for .ssh/id_rsa, which will be the private key file of the user, and it worked;

I saved the SSH private key as id_rsa, chmod 600 it, and gained access to the box as user over SSH;



PrivEsc


Inside the box as user, I have sudo permission to run a python program;

The code of the script;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/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()


Program Logic


The program, when executed, binds to a random port. When a connection is received, it asks for a password, which must be secretadminpassword. If the password is incorrect, the connection is killed. If the password is correct, user is given a bunch of options of the task they wish to perform. This choice is casted to an integer using int(), and the selected action, if valid, is performed. Should an exception occur during all this, the exception will be printed (print(e)), and a pdb (Python Debugger) shell will be spawned in the command prompt that invoked the script.

Exploit


A pdb shell is a powerful shell that can be used to access all the functionalities of the python interpreter. Since user input is directly casted to an integer, and any error will result in the pdb shell being launched, I was able to exploit this by simply connecting to the listener created by the script from another terminal using the locally installed netcat program, provide the valid password, and give a non-numeric input when asked for the task to perform;



Summary


  • Identified running services using nmap
  • Found an image upload functionality on the web page that does not validate data, and accepts URL uploads.
  • Discovered a hidden subdomain admin.forge.htb by fuzzing with ffuf. Access to the domain is only allowed to local hosts.
    • Exploited the URL upload functionality in the main forge.htb site to access the admin.forge.htb domain.
    • Dumping the HTML source of the homepage of the admin domain revealed a link to /announcements, which was found to contain an FTP credential for a user named user.
    • Exploited the URL upload in admin.forge.htb/upload to read the SSH private key of the user over FTP.
  • Inside the box as user;
    • user has access to run /opt/remote-manage.py as root using sudo
    • Exploited the python script to drop into a pdb shell for privesc.
This post is licensed under CC BY 4.0 by the author.
Contents