Secure Python Development

The Python programming language has gained a lot of popularity among developers due to the various solutions built into its architecture. Such solutions are strong dynamic typing, that is, the language does not allow implicit type conversions in ambiguous situations (for example, adding letters and numbers), while the type of a variable is determined at the time it is assigned a value and can change during the program. Also a useful feature of the Python language is automatic memory management and support for many programming paradigms, such as structural and functional programming. Finally, Python is a fully object-oriented language with support for introspection, that is, the ability to determine the type of an object at run time.

But, like any other programming language, Python has a number of vulnerabilities, which we will discuss in detail in this article. Since the article is aimed primarily at Python developers, we will talk about errors in the code that, for one reason or another, programmers can make during the development process. And we’ll start by looking at command injection.

Such different injections

First of all, let’s start with a definition. Command injection is a type of attack, the purpose of which is to execute arbitrary server OS commands or arbitrary SQL queries in the case of a DBMS.

Command injection allows you to execute almost any OS command with the rights of the current user. Thus, a shell command line launched by injecting /bin/sh into an application running as root will also run as root. The reason for this “strange” behavior of the code lies in the incorrect processing of data during dynamic evaluation of the executable code by the interpreter.

Even more interesting command injection options are the ability to provide remote access with the rights of the user running the applications. Thus, it is obvious that the execution of arbitrary commands through injection into applications written in Python is a rather dangerous vulnerability. Next, we will look at some examples of using unsafe Python commands.

Popen object

Team popenas well as commands popen3, popen4 execute the given string as a command, which makes it possible to inject arbitrary commands. Consider a small example:

 import os

user_input = "/etc && cat /etc/passwd 
os.popen("ls -l " + user_input)

Here we are in a variable user_input as user input, we get the path to the directory whose contents we want to display. But if the user passes as a parameter not only a directory, but also another command, then popen executes both commands. That is, in this case, it displays the contents of the /etc/passwd file.

Another problematic command is subprocess. Team subprocess allows you to create new processes, connect to their I/O/error channels, and get their return codes. This command allows you to create new processes, and the method call executes the given text string:

import subprocess 
import sys 
user_input = "/bin && cat /etc/passwd" 
subprocess.call("grep -R {} .".format(user_input), shell=True)

And another similar command that executes user-supplied data without any check is os.system:

import os 
user_input = "/etc && cat /etc/passwd" 
os.system("grep -R {} .".format(user_input))

SQL injection

SQL command injection is probably the most common type of command injection. SQL injection is a code injection technique that allows an attacker to insert or modify a SQL query in an application. To understand the problem, here is an example code snippet:

 def list_users():
  rank = request.args.get('rank', '') 
  if rank == 'admin':
    return "Can't list admins!"
  c = CONNECTION.cursor() 
  c.execute("SELECT username, rank FROM users WHERE rank = '{0}'".format(rank)) 
  data = c.fetchall()

Here, if the following string is passed as a request to the web application:

http://localhost:5000/users?rank=user’ UNION ALL SELECT * FROM SSN—

Then the application will return all records from the SSN table. SQL injections are a problem in most programming languages, not just Python.

General recommendations for protection

We looked at several examples of the use of unsafe commands and ways to exploit injections. Now is the time to talk about how you can protect yourself from such vulnerabilities. I want to note right away that command injections are not the only type of vulnerabilities that exist in Python, and in the following articles we will talk about other problems of this language.

So, the list of recommendations for developers can be started with a “common truth”, namely: Never trust user submitted data! Yes, the concept of zero trust has been around for years and everyone seems to be well aware that user-submitted data cannot be trusted, but command injection vulnerabilities can still be found, especially in web applications.

It is best to disable the execution of any commands entered by the user altogether. That is, make a list or some other interface in which the user can select the action that he needs. At the same time, it is best to limit as much as possible the input of arbitrary parameters that the user can pass to the command.

Let’s take a small example. Let’s use the command in our application find. We can “hardcode” the execution of this command, but leave the input of arbitrary parameters. It seems that the user cannot inject in its purest form, but find has an exec key that allows you to execute any command. And if our original application is launched under sudo or with a SUID bit, then after injecting the exec key with the desired value, the attacker will get root rights.

sudo find . -exec /bin/sh ; -quit

Thus, it is necessary to control not only the commands themselves entered by the user, but also the keys to the commands that they are going to use. In the case of the team find the application developer must strictly limit which keys and with what values ​​can be used. The user should be able to enter only the value of the mask of the searched file, all other parameters should be set using switches in the application interface.

In addition, you should not neglect the recommendations of the community regarding the use of secure commands and parameters.

Safe Commands

Consider the safe options from the code examples above:

user_input = "cat /etc/passwd” 
subprocess.popen (['ls', '-l', user_input ]) # команда отдельно

Here the user input will be processed separately from the command and the content user_input will not be executed as a command.

Team subprocess.check_output will check user input for commands:

subprocess.check_output('ls -l dir/’)# проверка ввода

Meaning shell=False won’t let you run the commands:

 subprocess.call('echo $HOME', shell=False)# запрет выполнения

And finally, checking the arguments passed to the application against the name of any existing file.

os.path.isfile(sys.argv [ 1])# проверка передаваемых аргументов

If for one reason or another it is impossible to avoid user input, you can use special libraries to control the input commands. Let’s see some examples of using such libraries.

Re module

The principle of the module is the use of regular expressions to check user-entered strings for potentially dangerous commands.

import re 
Python_code = input() 
Pattern = re.compile(‘re_command_pattern’) 
If pattern.fullmatch(python_code): 
   # выполнение python_code

Yaml module

Another user input control is the Yaml module. With the help of this module we can check if the file submitted by the user is potentially dangerous or if it is just a harmless file.

In the example below, we first check a plain text file to which the program does not react in any way, and then pass the path to the file /bin/ls as an input string and get a bunch of different error messages.

import yaml

user_input = input()
with open(user_input) as secure_file:
        contents = yaml.safe_load(secure_file)

Conclusion

In this article, we talked about command injection vulnerabilities in Python, as well as what tools can be used to deal with these vulnerabilities. In the next article, we will continue to look at the problems of insecure development in Python.


And now I recommend everyone to visit the open lesson “Fundamentals of OOP in Python”, where we will get acquainted with inheritance and learn how to work with classes. As a result of this lesson, you will:

– learn how to create your own classes;
– deal with inheritance;
– learn how to override methods and access superclass methods.

Sign up for an open class on the “Python Developer” specialization page.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *