Best practices to protect your Flask applications

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

💡
Want to improve your penetration testing skills? Try your skills out on the OWASP Vulnerable Flask App.

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_wtflibrary:

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.

Example of an xss attack that lets the user trigger forbidden actions

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 vs XSS: what's the difference?

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.

Attack pattern for CSRF

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:

  1. 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.

  1. 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.

  1. 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.

  1. 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.
  2. 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.
  3. Regular security audits: Conduct periodic security audits and vulnerability assessments to identify and address potential weaknesses. A faster and more reliable alternative to manual assessments is to use a dedicated API security testing tool with Flask support like Escape, which will test your application against 100+ security best practices and vulnerabilities directly in the CI/CD, and help you remediate all issues.

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 HTTPS, robust authentication and authorization mechanisms, token-based security, and rate limiting, developers can establish a solid foundation for API protection. Leveraging Flask extensions such as Flask-SSLify, Flask-RESTful, Flask-HTTPAuth, Flask-JWT-Extended, and Flask-Limiter streamlines the integration of these security measures into Flask applications.

For Flask enthusiasts integrating these security practices, consider running your applications on Gunicorn, not only boosts performance but also aligns you with the best in web development.

It is essential to tailor security strategies to the specific needs of your API, considering factors like the nature of data exchanged, user authentication requirements, and the level of exposure to potential threats. Regular security audits, automated API security testing and staying nformed about emerging security practices within the Flask community contribute to an adaptive and resilient security posture.

Want to learn more? 💡