Flask site penetration tests: security headers and the session cookie
Penetration testing is an easy way to check if your site is vulnerable to malicious attacks.
You created a Flask web application, it is running fine and using https. But is it secure enough? Did you do everything to protect your visitors, did you do everything to prevent malicious attacks?
A good way to proceed on this is to pentest your site. Penetration testing tools, or pen testing tools, can identifying security weaknesses. They identify vulnerabilities in the web application that can cause a security breach.
Wikipedia: The National Cyber Security Center, describes penetration testing as the following: 'A method for gaining assurance in the security of an IT system by attempting to breach some or all of that system's security, using the same tools and techniques as an adversary might.'
Most pentest tools must be installed and of course not on your local machine but somewhere on the internet so you can test your production site without going through firewalls, NAT. Someday I will look into these tools more in detail but for now I just wanted to have some indication of possible vulnerabilities.
I used an online test from Pentest-Tools.com, see link below, you get two free tests. Select the Website Vulnerability Scanner, enter the url of your site and sit back and wait for the results. Of course this is just a very very very basic test but at least it showed me that my site was missing security headers and that the session cookie was not secure. I also used Dareboost, see link below, their Website Speed Test and Website Analysis test gave me a nice Quality and Performance report.
Adding missing security headers
It is very easy to add the missing security headers to your Flask site. First I have a function adding the extra headers, I use the flag include_security_headers for sites actually on the internet.
from werkzeug.http import http_date
import datetime
def prepare_response_extra_headers(include_security_headers):
response_extra_headers = {
# always
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'Last-Modified': http_date(datetime.datetime.now()),
}
if include_security_headers:
response_security_headers = {
# X-Frame-Options: page can only be shown in an iframe of the same site
'X-Frame-Options': 'SAMEORIGIN',
# ensure all app communication is sent over HTTPS
'Strict-Transport-Security': 'max-age=63072000; includeSubdomains',
# instructs the browser not to override the response content type
'X-Content-Type-Options': 'nosniff',
# enable browser cross-site scripting (XSS) filter
'X-XSS-Protection': '1; mode=block',
}
response_extra_headers.update(response_security_headers)
return response_extra_headers
Then, in create_app() I add the extra headers to the response in after_request:
...
include_security_headers = config_name in ['staging', 'production']
...
# prepare extra response headers, see after_request
response_extra_headers = prepare_response_extra_headers(include_security_headers)
...
@app.after_request
def after_request(response):
# after_request is not called for 500 error
response.headers.extend(response_extra_headers)
return response
Note that we are using extend instead of update, see the Werkzeug datastructures information.
Making the session cookie secure
About the session cookie, my site is using https, http requests are redirected to https, so what is the problem? The problem is that the cookie does not need to come from your browser on the first (!) request. You can set the HTTP Strict Transport Security (HSTS) header and enforce https, but this does not solve the problem because not all browsers support this header.
The only way you can secure your session cookie is by adding the cookie secure attribute. I am still not sure if this solves everything but it is no effort to implement. In Flask we just do:
SESSION_COOKIE_SECURE = True
If you use Flask-Login and the 'remember me' functionality then also add:
REMEMBER_COOKIE_SECURE = True
REMEMBER_COOKIE_HTTPONLY = True
Another (unsolvable) security problem: passwords in memory
There is still another security problem when you run a site on a VPS (Virtual Private Server). Passwords are sent over https but are decoded at the server. Then, using good practices, they are encrypted and stored in the database.
Even if the server has been patched to eliminate vulnerabilities like Zombieload, a person having access to the server can monitor the memory of the server and see the unencrypted passwords. You can encrypt the password on the client using javascript but then the attacker still can use this encrypted value to log in. The minimum you can do is get a reliable VPS provider.
Summary
Without penetration testing you are just hoping for the best. Penetration testing is a very good way to check the vulnerabilities of your site. It is not very difficult but takes some / a lot of time. Remember that running a site on a VPS is never fully secure.
Links / credits
40 Best Penetration Testing (Pen Testing) Tools in 2020
https://www.guru99.com/top-5-penetration-testing-tools.html
Cookie Security for Flask Applications
https://blog.miguelgrinberg.com/post/cookie-security-for-flask-applications
Dareboost
https://www.dareboost.com/en
Data Structures
https://werkzeug.palletsprojects.com/en/0.15.x/datastructures/
HTTP Security Headers Analysis of Top One Million Websites
https://ccdcoe.org/uploads/2018/10/Art-18-HTTP-Security-Headers-Analysis-of-Top-One-Million-Websites.pdf
Mozilla Observatory
https://observatory.mozilla.org/
Penetration test
https://en.wikipedia.org/wiki/Penetration_test
Pentest-Tools.com
https://pentest-tools.com
Secure your Cookies (Secure and HttpOnly flags)
https://blog.dareboost.com/en/2019/03/secure-cookies-secure-httponly-flags/
Securing Cookies with HttpOnly and secure Flags
https://resources.infosecinstitute.com/securing-cookies-httponly-secure-flags/
Security Headers
https://securityheaders.com/
ZombieLoad attack lets hackers steal data from Intel chips
https://www.theverge.com/2019/5/14/18623708/zombieload-attack-intel-processors-speculative-execution
Read more
Flask Pentesting
Recent
- Hiding database UUID primary keys of your web application
- Don't Repeat Yourself (DRY) with Jinja2
- SQLAlchemy, PostgreSQL, maximum number of rows per user
- Show the values in SQLAlchemy dynamic filters
- Secure data transfer with Public Key encryption and pyNaCl
- rqlite: a high-availability and distributed SQLite alternative
Most viewed
- Using Python's pyOpenSSL to verify SSL certificates downloaded from a host
- Using UUIDs instead of Integer Autoincrement Primary Keys with SQLAlchemy and MariaDb
- Using PyInstaller and Cython to create a Python executable
- Connect to a service on a Docker host from a Docker container
- SQLAlchemy: Using Cascade Deletes to delete related objects
- Flask RESTful API request parameter validation with Marshmallow schemas