Best practices to protect your Flask applications
Want to know how to protect your Flask applications? Dive into our latest blog post, where we guide you through the best practices for Flask security. Explore how these techniques can not only enhance the security of your web applications but also bring tangible benefits to your development journey.
In this guide, Escape's security research team has gathered the most crucial tips to protect your Flask applications from potential breaches, including how to implement CSRF protection for Flask. Our goal is to empower you to create more resilient and efficient Flask projects. Let's get started!
What is Flask?
Flask is a lightweight web framework for Python that is used for building web applications. It provides the basic tools and features needed to create web-based applications, making it easier for developers to handle tasks like routing URLs, handling requests and responses, and rendering templates. Flask is known for its simplicity and flexibility, allowing developers to choose and integrate various components as needed, making it suitable for building a wide range of web applications, from simple websites to more complex web services and APIs.
But contrary to other mainstream Python frameworks for back-end, such as Django, Flask is a micro-framework, which means it provides the core functionality needed to build a web application but leaves many decisions to the developer. This flexibility can be an advantage, but it also means developers need to take more responsibility for security features.
Why is it important to secure Flask applications?
Securing Flask applications is critically important. Flask makes use of third-party modules, and that increases the risk of security breaches. Without robust security measures, these applications become vulnerable to a range of exploits, including injection attacks, cross-site scripting (XSS), and data breaches.
How to protect your Flask applications
OWASP Top 10
The OWASP Top 10 2023 list is a great starting point to enhance the security of your Flask applications.
It is especially valuable since it covers a broad spectrum of the most common and impactful security vulnerabilities. More than that, one of the key strengths of the OWASP Top 10 is its regular updates. By aligning your Flask application's security with the OWASP Top 10, you ensure that you're protected against the latest and most critical vulnerabilities.
Moreover, the OWASP community is a rich source of knowledge and support.
You can join OWASP Slack, listen in and ask for recommendations from the community. You'll be tapping into a vast network of expertise, which can be incredibly helpful in addressing complex security challenges
It contains the following vulnerabilities: HTML Injection, XSS, SSTI, SQL Injection, Information Disclosure, Command Injection, Brute Force, Deserialization, Broken Authentication, DOS, File Upload.
Adding security mechanisms to Flask: a python-specific example
Basic security practices are fundamental for Flask, such as employing strong cryptographic hashes for password storage, implementing protections against Cross-Site Request Forgery (CSRF) and Cross-Origin Resource Sharing (CORS), and protecting against SQL injection attacks.
Moving to package security, it's crucial to be aware of the nuances of using certain methods. A prime example is opting for the safe_load
method over load
in pyyaml to avoid potential vulnerabilities.
Let's look at the following example :
import yaml
# Unsafe method: Using `load` to parse YAML
yaml_data = """
!!python/object/apply:os.system
args: ['echo', 'Dangerous code executed!']
"""
try:
parsed_data = yaml.load(yaml_data)
except yaml.YAMLError as e:
print(f"Error: {e}")
In this code snippet, we use the load
method to parse YAML data. However, this method can be risky when dealing with untrusted input, as it allows arbitrary code execution, potentially leading to security vulnerabilities. It's important to avoid using load
with untrusted data.
Therefore the correct way to load a yaml file would be :
import yaml
# Safe method: Using `safe_load` to parse YAML
yaml_data = """
!!python/object/apply:os.system
args: ['echo', 'Dangerous code executed!']
"""
try:
parsed_data = yaml.safe_load(yaml_data)
except yaml.YAMLError as e:
print(f"Error: {e}")
Content type security is another critical aspect. This involves choices like preferring safe_load
in pyyaml, using libraries like defusedxml for XML parsing, and being vigilant about handling potentially malicious JSON inputs, such as overly long arrays.
You will first need to import ElementTree and declare a malicious XML payload.
from defusedxml import ElementTree as ET
# Unsafe XML data with malicious payload
xml_data = "<root><evil>&xxe;</evil></root>"
Instead of loading your xml using:
tree = ET.ElementTree(ET.fromstring(xml_data))
You would want to use:
tree_safe = ET.ElementTree(ET.fromstring(xml_data, forbid_dtd=True))
except ET.ParseError as e:
Here, we demonstrate the use of defusedxml
to parse potentially malicious XML data safely. The defusedxml
library helps mitigate XML external entity (XXE) attacks.
The security requirements also vary significantly depending on the industry. In the fintech sector, there's a strong emphasis on protecting Personally Identifiable Information (PII), such as social security numbers and bank account details. Similarly, those in the healthcare sector deal with HIPAA compliance, ensuring the confidentiality and security of medical information. In contrast, I have colleagues in cybersecurity who, despite not being under strict regulatory mandates, adhere to stringent security standards akin to those in regulated industries like finance and healthcare.
To the best of my knowledge, there are resources such as Flask-Security, which can be integrated into Flask applications to enhance security.
Securing your forms in Flask
Several steps should be taken to enhance your application's security. Firstly, it's essential to implement a robust CSRF (Cross-Site Request Forgery) solution for your forms. If you're using wtforms, you'll find that this functionality is conveniently built-in.
To do this you would just need to import the FlaskForm
from the flask_wtf
library:
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
class MyForm(FlaskForm):
username = StringField('Username')
submit = SubmitField('Submit')
@app.route('/form', methods=['GET', 'POST'])
def form():
form = MyForm()
if form.validate_on_submit():
# Process form data securely
return 'Form submitted successfully'
return render_template('form.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
Secondly, although client-side validation enhances user experience by providing immediate feedback, it's crucial to conduct server-side validation as well. This is because user input should never be trusted implicitly. Client-side validation is beneficial, but the real security lies in verifying the inputs on your server. If your application involves user file uploads, ensure the sanitization of file paths, using utilities like secure_filename
.
This is how you would do it:
from werkzeug.utils import secure_filename
def upload_file(file):
if file:
filename = secure_filename(file.filename)
# Save the file securely
file.save('uploads/' + filename)
return 'File uploaded successfully'
return 'No file uploaded'
Additionally, in contexts where HTML is passed, make sure it is automatically escaped to prevent injection attacks. Lastly, consider employing a tool like Talisman to reinforce other aspects of your application's security before going live. This comprehensive approach ensures a robust defense against various web vulnerabilities.
How to secure your Flask against XSS and CSRF
XSS : Cross-site scripting
XSS, or Cross-Site Scripting, is a type of security vulnerability typically found in web applications. It allows attackers to inject malicious scripts into web pages viewed by other users. This can happen when an application includes user input in its output without properly validating or escaping it.
For example, imagine you have a website with a comment section. If someone writes a comment containing a script and the website doesn't filter out the script from the comment, that script can run in another user's browser when they view the comment. This could potentially steal information from the user, like cookies or session tokens, or perform actions on their behalf.
Protecting Flask against XSS
Countering XSS (Cross-Site Scripting) attacks, particularly in Flask applications, involves a combination of practices and tools designed to sanitize user input and manage how data is rendered. Here are some key strategies:
Escape user input: Always escape user input before rendering it. Flask and its templating engine, Jinja2, automatically escape variables rendered in templates. This means that if you're using Jinja2's {{ variable }}
syntax, it will convert characters like <
, >
, and &
into their HTML-safe equivalents.
Safe templating practices: When using Jinja2, avoid using the |safe
filter or Markup
class unless absolutely necessary, as these can make your application vulnerable to XSS by marking a string as safe HTML.
Content security policy (CSP): Implement a Content Security Policy as an additional layer of protection. CSP is a browser feature that helps to detect and mitigate certain types of attacks, including XSS and data injection attacks. You can implement CSP in Flask using the Flask-Talisman extension.
Avoid inline JavaScript: As much as possible, avoid inline JavaScript, especially those involving user data. Inline JavaScript can easily become a vector for XSS if not handled carefully.
Use HttpOnly and secure cookies: Set the HttpOnly flag on cookies to prevent JavaScript from accessing them, which can be crucial in stopping certain types of XSS attacks where the attacker tries to steal session cookies.
Keep dependencies updated: Regularly update Flask and all dependencies to ensure you have the latest security fixes. Vulnerabilities in libraries can also lead to XSS vulnerabilities.
Use security extensions: Consider using Flask security extensions like Flask-SeaSurf to protect against CSRF, which can be related to XSS attacks.
CSRF : Cross-site request forgery
Cross-Site Request Forgery (CSRF) tricks a user into performing actions on a web application in which they're currently authenticated. Imagine you're logged into your bank's website, and you click on a malicious link or visit a malicious website. If that site contains a hidden form that sends a request to your bank's website (like a request to transfer money), the bank's website might process that request as if it were a legitimate request from you.
CSRF Protection in Flask
To prevent CSRF (Cross-Site Request Forgery) attacks in Flask applications, you can follow these steps:
Use the Flask-WTF extension: Flask-WTF is a popular extension that integrates with Flask forms. It provides CSRF protection by generating and validating CSRF tokens automatically. When you use Flask-WTF to handle your forms, each form is given a unique token that must be submitted with the form. This token is verified on the server to ensure the form submission is legitimate.
Generate CSRF tokens: If you’re not using Flask-WTF, ensure that every form generated by your server includes a CSRF token, a random, unique value that is checked when the form is submitted. You can use the Flask-SeaSurf extension as an alternative for generating and validating CSRF tokens.
Validate CSRF tokens: When a form is submitted, check that the CSRF token is present and valid. If it’s missing or incorrect, reject the request. This is automatically handled if you're using Flask-WTF or Flask-SeaSurf.
Set the SameSite attribute on cookies: If you are using cookies to manage sessions or user authentication, set the 'SameSite' attribute to 'Lax' or 'Strict'. This helps prevent CSRF attacks by controlling which requests the browser includes the cookies with.
Avoid state-changing GET requests: Ensure that GET requests (or any safe HTTP methods) do not perform any state-changing operations. Actions that alter data should be performed using POST, PUT, DELETE, or other appropriate HTTP methods, which can be protected with CSRF tokens.
How to protect APIs built with Flask
To protect your APIs built with Flask, it is essential to address several key security aspects:
- Use HTTPS: Employing HTTPS encrypts data in transit, preventing unauthorized access and mitigating the risk of man-in-the-middle attacks.
from flask import Flask
from flask_sslify import SSLify
app = Flask(__name__)
sslify = SSLify(app)
The Flask-SSLify
extension is a simple way to enforce HTTPS in a Flask application. When included in the app, it automatically redirects all HTTP requests to their HTTPS counterparts, ensuring secure communication between clients and the server. By implementing HTTPS, sensitive data exchanged during API requests, such as authentication tokens or user credentials, is encrypted, mitigating the risk of eavesdropping and data tampering.
Additionally, configure your server environment to enable HTTPS at the server level, utilizing a valid SSL/TLS certificate. This extension complements server-level configurations, enhancing the overall security of your Flask API.
- Authentication and Authorization with Flask-RESTful:
The code snippet below demonstrates the use of Flask-RESTful, Flask's extension for building RESTful APIs, along with Flask-HTTPAuth for implementing basic authentication.
from flask import Flask
from flask_restful import Api, Resource
from flask_httpauth import HTTPBasicAuth
app = Flask(__name__)
api = Api(app)
auth = HTTPBasicAuth()
@auth.verify_password
def verify_password(username, password):
# Add your authentication logic here
return True
class SecureResource(Resource):
@auth.login_required
def get(self):
return {'message': 'This is a secure endpoint'}
The HTTPBasicAuth
decorator ensures that the verify_password
function is called before any request to the SecureResource
endpoint is processed. Developers should replace the placeholder verify_password
function with their authentication logic, validating user credentials before granting access to the protected resource.
3. For a more sophisticated authentication setup, consider using token-based authentication, especially for APIs intended to be consumed by client-side applications.
from flask import Flask
from flask_jwt_extended import JWTManager, jwt_required, get_jwt_identity
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-secret-key'
jwt = JWTManager(app)
@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
current_user = get_jwt_identity()
return {'message': f'Hello, {current_user}!'}
Flask-JWT-Extended, as demonstrated in the snippet above, provides a robust solution for token-based authentication in Flask applications.
The JWTManager
is configured with a secret key, and the @jwt_required()
decorator is applied to the /protected
endpoint, ensuring that only requests containing a valid JWT are allowed. The get_jwt_identity
function retrieves the identity (user information) from the token, providing access to specific user details within the protected endpoint.
Periodically rotate the JWT secret key to enhance security. Additionally, consider implementing token expiration times to minimize the potential impact of compromised tokens. Flask-JWT-Extended offers flexibility in configuring token expiration, providing a robust approach to token management.
- Rate Limiting with Flask-Limiter:
from flask import Flask
from flask_limiter import Limiter
app = Flask(__name__)
limiter = Limiter(app, key_func=lambda: request.headers.get('X-Real-IP'))
@app.route('/limited', methods=['GET'])
@limiter.limit("5 per minute")
def limited():
return {'message': 'This endpoint has rate limiting'}
Flask-Limiter is integrated into this example to demonstrate rate limiting, a crucial security measure to protect against abuse, DDoS attacks, and unintended excessive API usage. The Limiter
object is created, and a rate limit of "5 requests per minute" is set for the /limited
endpoint. By applying the @limiter.limit
decorator, developers can restrict the rate at which clients can make requests to this endpoint, preventing potential abuse or overwhelming of resources.
Customize rate limits based on your application's specific needs. For instance, adjust limits per endpoint, user, or IP address. Regularly monitor and adjust rate limits based on actual usage patterns to strike a balance between security and usability. Additionally, consider implementing gradual rate limiting strategies to avoid sudden service disruptions due to unexpected traffic spikes.
5. In addition to these measures, server hardening is also imperative. Ensuring your server is secure involves minimizing unnecessary exposures, such as restricting SSH access to specific IPs or disabling it entirely. Providers like Heroku offer environments that are secure by default.
6. If your API involves file uploads, enforce strict controls on allowed file types, limit file sizes, and employ mechanisms like virus scanning to prevent malicious uploads.
7. CORS headers: Implement Cross-Origin Resource Sharing (CORS) headers to control which domains can access your API. This helps prevent unauthorized domains from making requests and enhances overall security.
8. Encrypted storage: If your API involves storing data, ensure that sensitive information is stored securely using encryption. This includes user credentials, API keys, and any other sensitive data.
9. Regular security audits: Conduct periodic security audits and vulnerability assessments to identify and address potential weaknesses.
Conclusion
In conclusion, securing APIs built with Flask is a multifaceted task requiring a comprehensive approach to mitigate potential risks and ensure the confidentiality, integrity, and availability of data. By implementing the above best practices you can significantly increase the security posture of your Flask APIs. For optimal performance and alignment with best practices, consider running your applications on Gunicorn.
A faster and more reliable alternative to manual assessments is to use a dedicated API security testing tool with Flask support like Escape, which can scan all your exposed API endpoints within minutes and help you remediate any issues. The best part is that we do all this without traffic monitoring.
Want to see how Escape can secure your APIs? Request a demo today, and let us show you how.