Python Security Essentials for Your Apps

Sharon YelenikSharon Yelenik
9 min read

In July 2020, Twitter faced a serious security breach where high-profile accounts were compromised, leading to unauthorized tweets soliciting Bitcoin. This incident highlighted the vulnerability of online platforms to cyberattacks and underscored the urgent need for robust security measures in today's digital landscape.

Safeguarding applications against malicious actors is essential. Whether running a small e-commerce site or a large-scale social media platform, prioritizing security is non-negotiable.

In this article…

We'll explore how developers can fortify their e-commerce and other platforms against malicious actors like in the Twitter breach.

First, we'll explore Python's built-in security features and libraries to bolster protection against password vulnerabilities, MIME sniffing, clickjacking attacks, SQL injections, and more.

Next, we'll introduce key third-party tools such as Cloudinary, OWASP Dependency-Check, and Bandit, which automate crucial security tasks that can be challenging to manage manually. These tools handle file security, dependency vulnerability checks, and code base vulnerability scanning, ensuring your app remains protected against potential attacks.

Finally, we'll highlight some of those coding best practices that should be part of your routine.

Notes:

  • Find this app on GitHub, reflecting the concepts discussed in this blog post.

  • Watch a video that walks you through the code.

Understanding Built-in Security Features and Middleware

Your first line of defense for security in your Python app is the built-in security features and middleware provided by your Django (or Flask) framework. This functionality goes a long way in addressing OWASP’s (Open Web Application Security Project) top ten vulnerabilities. Here are some measures easily accessible in the Django framework:

Password Validation

Django provides tools to ensure robust password security. This includes features such as checking that the password isn't too similar to the user's attributes, setting a minimum length (default is 8 characters), blocking commonly used passwords, and verifying that the password isn't entirely numeric. Additionally, you can tailor validation rules to include, for example, a combination of characters, digits, and symbols for enhanced security.

To make sure these validations are implemented in your app, and then change the default minimum length to 12 characters:

  1. In your project’s settings.py file, make sure the built-in validators are listed under the AUTH_PASSWORD_VALIDATORS setting.
AUTH_PASSWORD_VALIDATORS = [
   {
       'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
   },
]
  1. Add 'OPTIONS': {'min_length': 12}, to the 'django.contrib.auth.password_validation.MinimumLengthValidator' validator:
AUTH_PASSWORD_VALIDATORS = [
   {
       'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
       'OPTIONS': {'min_length': 12},
   },
   {
       'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
   },
]
  1. Save the settings.py file and restart your Django development server to apply the changes.

For customizing additional validation rules, refer to the documentation.

Middleware Magic

Django's middleware offers a suite of security measures, including:

  • SecurityMiddleware: Sets default security headers to prevent various code injection attacks. Headers like 'X-Content-Type-Options' prevent MIME sniffing attacks by instructing the browser not to guess file types based on content. This prevents exploitation by disguising a malicious script as an innocent image file.

  • CsrfViewMiddleware: Mitigates CSRF attacks by verifying form submissions originate from your application, thus preventing malicious requests when users are tricked into submitting them through deceptive links or emails. Include the CSRF token in your Django templates using the {% csrf_token %} template tag.

  • XFrameOptionsMiddleware: Protects against clickjacking attacks by blocking your webpage from being embedded in frames or iframes. This ensures that malicious interactive elements can't be concealed to deceive users into performing actions that expose sensitive data or functionalities.

  • AuthenticationMiddleware: Controls access based on user authentication.

By implementing these built-in security features and middleware, you can significantly enhance the security of your Python web application against common vulnerabilities.

To integrate these middleware into your Django app:

  1. In your project’s settings.py file, make sure these features are included under the MIDDLEWARE setting.
MIDDLEWARE = [
 ...
 'django.middleware.security.SecurityMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 ...
]
  1. Add the {% csrf_token %} to your forms in your HTML
   {% block body %}
     <div id='backend_upload'>
       <form action="{% url "upload" %}" method="post" enctype="multipart/form-data">
         {% csrf_token %}
         {{ backend_form }}
         <input type="submit" value="Upload">
       </form>
     </div> 
   {% endblock %}
  1. Refer to [How to Set Up Signup, Login, and Logout using Django's Middleware](https://dev.to/sharony/how-to-set-up-signup-login-and-logout-using-djangos-middleware-36l4) for full instructions about how to set up authentication in your app.
  1. Authorize actions exclusively for authenticated users in views.py:
# views.py

from django.shortcuts import render

def my_view(request):
    if request.user.is_authenticated:
        # User is authenticated, perform user-specific actions
        username = request.user.username
        # Other user-related logic
    else:
        # User is not authenticated, handle anonymous user case
        pass
    return render(request, 'my_template.html')
  1. Authorize actions exclusively for authenticated users in templates like my_template.html: Use template variables like {{ user }}, {{ user.username }}, or {% if user.is_authenticated %} to display user-specific content or customize the user interface based on the user's authentication status.
<!-- my_template.html -->


{% if user.is_authenticated %}
 <p>Welcome, {{ user.username }}!</p>
 <!-- Display user-specific content -->
{% else %}
 <p>Welcome, Guest!</p>
 <!-- Display content for anonymous users -->
{% endif %}

ORMs (Object-Relational Mapping)

An ORM serves as a protective layer for your data interactions with the database, ensuring both data integrity and security. By automating parameterized queries, which separate SQL code from user input, ORMs effectively prevent SQL injection attacks. Furthermore, ORMs bolster security by performing data validation, including checks on string lengths and integer ranges, thus reducing potential vulnerabilities.

If you've run migrations using the manage.py command (e.g., python manage.py makemigrations and python manage.py migrate), then you're using Django ORM to manage your database schema.

Here’s some code that illustrates how the Django ORM enforces SQL security:

# upload_app/models.py
from django.db import models

class Photo(models.Model):
   image = models.ImageField(upload_to='images/') # Secure file uploads
   transformed_image = models.URLField(blank=True, null=True) # Secure URL storage

# Example of parameterized query (data insertion)
@classmethod
def create_photo(cls, image_path):
   photo = cls(image=image_path) # Creating a new Photo instance with parameterized data
   photo.save() # Saving the instance to the database
   return photo

# Example of data validation (ensuring image size doesn't exceed a certain limit)
def save(self, *args, **kwargs):
   # Check if the image file size is within a specific limit (e.g., 10MB)
   if self.image.size > 10 * 1024 * 1024: # 10MB limit raise ValueError("Image size exceeds the maximum allowed limit.")
   super().save(*args, **kwargs) # Call the parent class's save method to save the object to the database

Integrating Third-Party Tools for Enhanced Security

Achieving comprehensive security may involve complex efforts that can be challenging to implement independently. This is where third-party tools come into play, offering automation for key security processes. Let’s take a look at three third-party tools that can help you keep your apps secure: Cloudinary for file security, OWASP Dependency-Check for scanning dependencies, and Bandit for identifying vulnerabilities in your Python code.

Cloudinary: Handles File Security

Allowing file uploads to your website can expose it to security risks, including unauthorized access and data breaches. Attackers may exploit vulnerabilities in the upload process to introduce malicious files or execute harmful scripts. Cloudinary, a comprehensive platform for image and video management, offers a range of file-handling features, including robust security measures that address these risks. Cloudinary checks your files and user uploads to stop any viruses or harmful code from reaching your web and mobile viewers, ensuring compliance with security standards by thoroughly inspecting for malware and validating files before they're accepted.

Additionally, Cloudinary's moderation tools can assess uploaded images to ensure appropriate content, maintaining a safe and compliant environment by detecting and filtering out potentially offensive material before it's publicly accessible, promoting a safe and user-friendly experience for website visitors.

Explore Cloudinary’s offerings in its documentation.

Note: Moderation tools in Cloudinary are available as add-ons, with usage limits determined by your subscription plan.

To implement file security with Cloudinary:

  1. Sign up for an account and obtain your API credentials. Watch this quick video to install and configure Cloudinary in your Python app.

  2. Visit the Add-on page of the Cloudinary Console Settings, and register for the Rekognition Moderation AI and Perception Point addons.

  3. Adapt and integrate the provided code as needed to ensure robust file security.

import cloudinary.uploader
import cloudinary.utils

# Configure Cloudinary with your credentials
cloudinary.config(
    cloud_name = 'your_cloud_name',
    api_key = 'your_api_key',
    api_secret = 'your_api_secret'
)

# Upload a file securely using HTTPS, name it new_image, and automatically run it through moderation to block inappropriate and malicious files from being uploaded.

upload_result = cloudinary.uploader.upload('path/to/your/image.jpg', public_id='new_image', secure=True, moderation=’aws_rek|perception_point')

if result['moderation'][0].get('status') == 'approved'
    print('Secure upload successful:', 
       upload_result['secure_url'])
    # Display the image in an HTML template.
else:
    print('The image failed moderation'])

OWASP Dependency-Check: Scans Dependencies for Vulnerabilities

You could be introducing risks to your project, unbeknownst to you, via your project dependencies. OWASP Dependency-Check automates the identification of vulnerabilities within these dependencies. This significantly enhances your application's security, enabling early detection and prompt resolution of vulnerabilities during the development process.

Here’s how to install OWASP Dependency-Check and scan your project:

  1. Go to the OWASP DependencyCheck repo on GitHub.

  2. Scroll down to Releases and click the latest release to download the ZIP file.

  3. Unzip the file from your terminal by entering:unzip ~Downloads/dependency-check-<version_number>-release.zip

  4. Change to the dependency-check/bin directory by entering cd dependency-check/bin.

  5. View the files in the directory by entering ls. You should see dependency-check.bat and dependency-check.sh.

  6. Enter pwd to obtain the file path.

Here’s how to scan your project:

  1. Change directories so that you’re in the directory of the project you want to scan.

  2. In the command prompt, enter <dependency-check-path>/dependency-check.sh –scan <project_name>

Note: If it’s the first time you’re running Dependency-Check, it might take a while while the list of all vulnerabilities is being downloaded.

3. When the report is generated, view it as a web page by entering firefox dependency-check-report.html. The report provides a summary showing your project files and all the third-party vulnerabilities found along with their severity. Scroll down to drill down on all the vulnerabilities and advice on what to do next.

Bandit: Scans Your Code for Vulnerabilities

Dealing with every security flaw in your codebase manually can be overwhelming. Instead, Bandit offers automated vulnerability detection for identifying potential security issues in your code.

To scan your project with Bandit:

  1. Install Bandit using pip:
pip install bandit
  1. Navigate to the directory containing your Python code and simply run the following command to scan all the files in your project, including low-severity issues:
bandit -r . -ll

Additional Security Best Practices

In addition to relying on Python’s robust built-in security measures and third-party tools, make sure you adhere to security best practices within your coding routine. For example, always hash and encrypt passwords and sensitive data using libraries like fernet and cryptography. Moreover, ensure that your API keys and secrets are never exposed, utilizing secret management tools provided by the os library or employ environment variables instead. Additionally, maintain vigilance with user input validation, error handling and the use of SQL-prepared statements for secure database interactions.

Conclusion

Maintaining Python security in your applications demands careful attention. Protect yourself from potential tampering by leveraging built-in security features and middleware offered by Python.

Additionally, integrating essential third-party tools is crucial for addressing tasks beyond your capabilities, such as ensuring file security and checking for vulnerabilities in dependencies and your codebase. Automate these processes using tools like Cloudinary, OWASP Dependency-Check, and Bandit.

And don’t forget to make security a priority in your coding routine.

Protecting your Python applications is crucial for their safety and integrity. Act now to implement these safeguards effectively.

4
Subscribe to my newsletter

Read articles from Sharon Yelenik directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sharon Yelenik
Sharon Yelenik