So far, we have seen four types of security issues with Python, namely, those with reading input, evaluating expressions, overflow errors, and serialization issues. All our examples so far have been with Python on the console.
However, almost all of us interact with web applications on a daily basis, many of which are written in Python web frameworks such as Django, Flask, Pyramid, and others. Hence, it is more likely that we are exposed to security issues in such applications. We will look at a few examples here.
Server Side Template Injection (SSTI) is an attack using the server-side templates of common web frameworks as an attack vector. The attack uses weaknesses in the way user input is embedded on the templates. SSTI attacks can be used to figure out internals of a web application, execute shell commands, and even fully compromise the servers.
We will see an example using a very popular web application framework in Python, namely, Flask.
The following is the sample code for a rather simple web application in Flask with an inline template:
# ssti-example.py from flask import Flask from flask import request, render_template_string, render_template app = Flask(__name__) @app.route('/hello-ssti') defhello_ssti(): person = {'name':"world", 'secret': 'jo5gmvlligcZ5YZGenWnGcol8JnwhWZd2lJZYo=='} 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) if __name__ == "__main__": app.run(debug=True)
Running it on the console, and opening it in the browser allows us to play around with the hello-ssti
route:
$ python3 ssti_example.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger pin code: 163-936-023
First, let's try some benign inputs:
Here is another example.
Next, let's try with some crafty inputs which an attacker may use.
What is happening here?
Since the template uses unsafe %s
string templates, it evaluates anything that is passed to it into Python expressions. We passed {{ person.secret }}
, which, in the Flask templating language (Flask uses Jinja2 templating), got evaluated to the value of the key secret
in the dictionary person
, effectively exposing the secret key of the app!
We can perform even more ambitious attacks, as this hole in the code allows an attacker to try the full power of Jinja templates, including for
loops. Here is an example:
The URL used for the attack is as follows:
http://localhost:5000/hello-ssti?name={% for item in person %}<p>{{ item, person[item] }}</p>{% endfor %}
This goes through a for loop, and tries to print all contents of the person
dictionary.
This also allows an attacker easy access to the sensitive server-side configuration parameters. For example, he can print out the Flask configuration by passing the name parameter as {{ config }}
.
Here is the screenshot of the browser, printing the server configuration using this attack.
We saw in the previous section some examples of using server side templates as an attack vector to expose sensitive information of the web application/server. In this section, we will see how the programmer can safeguard his code against such attacks.
In this specific case, the fix for this is to use the specific variable that we want in the template, rather than the dangerous, allow-all %s
string. Here is the modified code with the fix:
# ssti-example-fixed.py from flask import Flask from flask import request, render_template_string, render_template app = Flask(__name__) @app.route('/hello-ssti') defhello_ssti(): person = {'name':"world", 'secret': jo5gmvlligcZ5YZGenWnGcol8JnwhWZd2lJZYo=='} if request.args.get('name'): person['name'] = request.args.get('name') template = '<h2>Hello {{ person.name }} !</h2>' return render_template_string(template, person=person) if __name__ == "__main__": app.run(debug=True)
Now the earlier attacks all fizzle off.
Here is the browser screenshot for the first attack:
Here is the browser screenshot for the next attack.
Now let's look at another attack that is commonly used by malicious hackers, namely, Denial of Service (DoS).
DoS attacks target vulnerable routes or URLs in a web application, and sends them crafty packets or URLs, which either force the server to perform infinite loops or CPU-intensive computations, or force it to load huge amounts of data from databases, which puts a lot of load on the server CPU, preventing the server from executing other requests.
We will see a minimal example of a DoS attack using a variation of our previous example:
# ssti-example-dos.py from flask import Flask from flask import request, render_template_string, render_template app = Flask(__name__) TEMPLATE = ''' <html> <head><title> Hello {{ person.name }} </title></head> <body> Hello FOO </body> </html> ''' @app.route('/hello-ssti') defhello_ssti(): person = {'name':"world", 'secret': 'jo5gmvlligcZ5YZGenWnGcol8JnwhWZd2lJZYo=='} if request.args.get('name'): person['name'] = request.args.get('name') # Replace FOO with person's name template = TEMPLATE.replace("FOO", person['name']) return render_template_string(template, person=person) if __name__ == "__main__": app.run(debug=True)
In the preceding code, we use a global template variable named TEMPLATE
, and use the safer {{ person.name }}
template variable as the one used with the SSTI fix. However, the additional code here is a replacement of the holding name FOO
with the name value.
This version has all the vulnerabilities of the original code, even with the %s
code removed. For example, take a look at the following screenshot of the browser exposing the {{ person.secret }}
variable value in the body, but not in the title of the page.
This is due to this following line of code that we added shown as follows:
# Replace FOO with person's name template = TEMPLATE.replace("FOO", person['name'])
Any expression passed is evaluated, including the arithmetic ones. For example:
This opens up pathways to simple DoS attacks by passing in CPU-intensive computations that the server cannot handle. For example, in the following attack, we pass in a very large computation of a number, which occupies the CPU of the system, slows the system down and makes the application non-responsive:
is attack is http://localhost:5000/hello-ssti?name=Tom {{ 100*100000000 }}
.
By passing in the arithmetical expression {{ 100**100000000 }}
, which is computationally intensive, the server is overloaded and cannot handle other requests.
As you can see in the previous screenshot, the request never completes, and also prevents the server from responding to other requests; as you can see from how a normal request to the same application on a new tab opened on the right side is also held up causing the effect of a DoS style attack:
The code that we used in the earlier section to demonstrate a minimalistic DOS attack is also vulnerable to script injection. Here is an illustration:
The URL used for this attack is as follows:
http://localhost:5000/hello-ssti?name=Tom<script>alert("You are under attack!")</script>
These kinds of script injection vulnerabilities can lead to XSS, a common form of web exploit where attackers are able to inject malicious scripts into your server's code, which are loaded from other websites, and take control of it.
We saw a few examples of DoS attacks and simple XSS attacks in the previous section. Now let's look at how the programmer can take steps in his code to mitigate such attacks.
In the previous specific example that we have used for illustration, the fix is to remove the line that replaces the string FOO
with the name value, and to replace it with the parameter template itself. For good measure, we also make sure that the output is properly escaped by using the escape filter, |e
, of Jinja 2. Here is the rewritten code:
# ssti-example-dos-fix.py from flask import Flask from flask import request, render_template_string, render_template app = Flask(__name__) TEMPLATE = ''' <html> <head><title> Hello {{ person.name | e }} </title></head> <body> Hello {{ person.name | e }} </body> </html> ''' @app.route('/hello-ssti') defhello_ssti(): person = {'name':"world", 'secret': 'jo5gmvlligcZ5YZGenWnGcol8JnwhWZd2lJZYo=='} if request.args.get('name'): person['name'] = request.args.get('name') return render_template_string(TEMPLATE, person=person) if __name__ == "__main__": app.run(debug=True)
Now that both of the vulnerabilities are mitigated, the attacks have no effect, and fail harmlessly.
Here is an screenshot demonstrating the DoS attack .
Here is the one, demonstrating the XSS attack.
Similar vulnerabilities due to bad code in server side templates exist in other Python web frameworks such as Django, Pyramid, Tornado, and others. However, a step-by-step discussion on each of these is beyond the scope of this chapter. The interested reader is directed to security resources on the web discussing such issues.
3.16.75.165