Python is a high-level programming language and offers a lot of flexibility and liberty to structure your codebase the way you want it which might open up some loopholes if security practices are not being followed.
When building software, writing secure code is essential for protecting sensitive data and maintaining correct behaviour of software. Writing secure code can be hard though and even the best developers can’t always be 100% sure of the security of their code.
No matter how small the application and or how good the developers who built it are, there’s always a possibility of a security vulnerability that could be exploited to steal money from users or be the next known Cyber-Incident.
So what do you do to prevent or avoid this?
The best thing to do here is to follow best practices for writing secure code when building applications using Python. In this article, we’ll explore best practices for securing python code from the simplest practices to the hardest one.
Use the Most Recent Major Version of Python
Many companies and developers are still running old versions of Python for their projects and even in production like Python 2.6 or 2.7. These are way outdated and won’t receive any more security updates after April 2020.
Python 3 was released date back in 2008 and starting from Jan 1, 2020 the Python Foundation announced that Python 2 will stop receiving security updates or support from the community.
If you are still using old versions of Python below Python 3, then you should start considering how to migrate your codebase to Python 3. Start using Python 3 for your new projects or you leave yourself open to security vulnerabilities.
To check for your Python version, you can run:
Migrating your Python 2 code to Python 3 is actually very easy and you can read more on how to do that here.
Use a Virtual Environment
When building any Python projects, it’s always advisable to use a virtual environment as it helps to prevent conflict in Python modules and as well as have the same modules both on local and production environments.
Using a virtual environment prevents having malicious Python dependencies in your projects and shipping the same to production by using `pip freeze` to generate requirements.txt. If you have malicious packages in your Python environments, using a virtual environment will prevent having the same packages in your Python codebase since it’s isolated.
To create a virtual environment you can either use Virtualenv or Pipenv which help create isolated virtual environments. Pipenv helps to manage, have a predictable and up-to-date environment.
With Pipenv, you can manage your installations, virtual environments, look through your dependency tree, and scan your dependencies for known vulnerabilities.
You can set up Virtualenv:
pip install virtualenv
virtualenv -p /path/to/python <env_name>
Import Packages the Right Way
When working with external or internal Python modules, you should always ensure you are importing them the right way and using the right paths. We have two types of import paths in Python and they are absolute, relative.
Absolute imports specifies the path of the resource to be imported using its full path from the project’s root folder while relative import specifies the resource to be imported relative to the current location of the project where the import statement is.
Now there are two types of relative imports: implicit and explicit.
Implicit imports does not specify the resource path relative to the current module while Explicit imports specify the exact path of the module you want to import relative to the current module.
Implicit import has been disapproved and removed from Python 3, because if the module specified is found in the system path, it will be imported and that could be very dangerous.
Since it’s possible for a malicious module with the same name to be in a popular open source library and find its way to the system path. If the malicious module is found before the real module it will be imported and could be used to exploit applications that has it in their dependency tree.
To prevent this, ensure you use either absolute import or explicit relative imports as it guarantees you import the real and intended module.
If you are still using Python 2, ensure you remove the use of implicit relative imports as this as been removed in Python 3.
String Formatting In Python
Python has one of the most powerful and flexible methods to format strings and if you are not careful enough while using, you might end up opening up a security vulnerability in your code.
Python3 introduced f-strings and str.format() as a flexible way to format strings and its actually very interesting. However, this opens up a way for data exploit when dealing with user inputs.
If the application built on Python allows users control of the format string, they can be misused to leak sensitive data. For instance, let’s take a look at the exploit code below:
With this, sensitive global data from a CONFIG dictionary can be accessed via the argument.
However, Python has a built-in string module that can be used to fix and prevent this. Using the Template class from the string module:
from string import Template
name_template = Template(“Hello, my name is $name.”)
greeting = name_template.substitute(name=”Tobi”)
/* Hello, my name is Toby */
The string module is good for handling user inputs and generated data.
Handle Python HTTP Requests Safely
When building Python project that requires sending HTTP requests, it’s always advisable to do it safely and know the library you are using handles security to prevent security issues.
When using HTTP requests library like Requests, you should not pin the versions down in your requirements.txt has that might install outdated and vulnerable version of the module.
For instance, Requests uses Certifi for handling SSL verification, ensure you are sending it to a non-exploited site. By default, Requests handles the SSL certificate verification and can be disabled if you trust the source.
url = “http://trusted_url”
This ensures you are not sending requests to an exploited source that could send back exploited code in the Response headers or body.
So to prevent this ensure you are using the latest version of your HTTP requests library, confirm if the library is handling the SSL verification of the source you sent requests to, if you are using standard library urllib, you should follow best practices to prevent request smuggling.
Look Out for Exploited and Malicious Packages
Packages can be very helpful and save you time as you don’t have to re-invent the wheel. Packages can be easily installed through the Pip package. They offer various benefits like saving time, making your codebase compact and smaller, easier application design and better performance.
Most Python Packages are published to PyPI which serves as a code repository for Python Packages and does not go through any form of security review or check.
This means that anyone out there with malicious thought can easily build and publish a package to PyPI with a malicious code or sometimes publish a package with a similar name to a popular package and imitate the package features.
Double-check each Python packages you are installing and importing to prevent having exploited packages in your code. Also, you can use security tools to scan your Python dependencies to screen out exploited packages.
Handling Data Deserialization Safely
When handling data deserialization in Python, I’ll recommend only deserializing data from a trusted source as its possible that a malicious arbitrary code could be hidden in the data.
Deserialization process in Python recreates Python objects by reading its representation from a file on disk, network interface or string. The resulting objects contain constructors and methods that are executable.
So if data contains malicious code, on deserialization it could run the code thereby exploiting user data or doing something worse.
To fix it, ensure you are using deserialization packages that ensures the safety of the data in sandbox before fully deserializing the data. One of the best packages to do this is PyCrypto as it securely deserializing your data and prevent the running of arbitrary code.
The same goes for Pickle and YAML data type. Pickle lets you to serialize and deserialize a Python object structure. If you are deserializing a pickled python object structure from an untrusted source, that can result in malicious code execution.
YAML is another type of data type mostly used for data configurations and be handled using the PyYAML package. However if you have a YAML objects with malicious code, using the yaml.load function won’t help but lets you run malicious code if found.
This can be prevented by using the yaml.safe_load for preventing running malicious code when deserializing YAML data in Python.
Keep Up-To Date Open Source Vulnerabilities in Your Python Packages
One of the simplest ways to prevent and get rid of open source vulnerabilities is having the latest updates of the open source that already fixed the vulnerability. Open source is a good way for developers and communities with one interest in mind to build, contribute and publish software openly for better use of the community.
However, sometimes there’s a possibility that a security loophole might pop up that could be very dangerous as any software or application using the project may be open up for attacks.
For this reason, open source vulnerabilities are always published as soon as they are discovered and a fix and prevention method are usually rolled out in the next version usually security patch release which should end up in the next major release.
The sooner you have the latest update of the open source package, the better you are secured. Always ensure you are updated with vulnerabilities of the open source package you are using, so as to know when to upgrade to the next version.
Securing your Python code is actually very easy to implement as long as you follow the basic rules and practices.
Many developers do not put this in mind as they are only concerned on delivering and meeting up with tasks which can open ways for vulnerability in the future.
It’s always advisable to have these practices in mind when you are writing your Python projects.