Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I prevent execution of arbitrary commands from a Django app making system calls?

I have a Django application I'm developing that must make a system call to an external program on the server. In creating the command for the system call, the application takes values from a form and uses them as parameters for the call. I suppose this means that one can essentially use bogus parameters and write arbitrary commands for the shell to execute (e.g., just place a semicolon and then rm -rf *).

This is bad. While most users aren't malicious, it is a potential security problem. How does one handle these potential points of exploit?

EDIT (for clarification): The users will see a form that is split up with various fields for each of the parameters and options. However some fields will be available as open text fields. All of these fields are combined and fed to subprocess.check_call(). Technically, though, this isn't separated too far from just handing the users a command prompt. This has got to be fairly common, so what do other developers do to sanitize input so that they don't get a Bobby Tables.

like image 795
gotgenes Avatar asked Apr 20 '09 15:04

gotgenes


People also ask

What is command execution vulnerability?

An OS command injection is a web security vulnerability that enables the execution of unauthorized operating system commands. An OS command injection vulnerability arises when a web application sends unsanitized, unfiltered system commands to be executed.

What is blind OS command injection?

Executing a Command Injection attack simply means running a system command on someone's server through a web application or some other exploitable application running on that server. Executing a Blind Command Injection attack means that you are unable to see the output of the command you've run on the server.

What is the name of the form field that is vulnerable to command injection?

XML external entity injection (XXE) This vulnerability can cause exposure of sensitive data, server-side request forgery (SSRF), or denial of service attacks.

What is command execution?

Command injection or also known as Remote Code Execution in terms of web exploitation, can be possible to a certain website accepts added strings of characters or arguments; the inputs are used as arguments for executing the command in the website's hosting server.


4 Answers

Based on my understanding of the question, I'm assuming you aren't letting the users specify commands to run on the shell, but just arguments to those commands. In this case, you can avoid shell injection attacks by using the subprocess module and not using the shell (i.e. specify use the default shell=False parameter in the subprocess.Popen constructor.

Oh, and never use os.system() for any strings containing any input coming from a user.

like image 151
Rick Copeland Avatar answered Sep 26 '22 21:09

Rick Copeland


By never trusting users. Any data coming from the web browser should be considered tainted. And absolutely do not try to validate the data via JS or by limiting what can be entered in the FORM fields. You need to do the tests on the server before passing it to your external application.

Update after your edit: no matter how you present the form to users on your front-end the backend should treat it as though it came from a set of text boxes with big flashing text around them saying "insert whatever you want here!"

like image 21
Michael Cramer Avatar answered Sep 24 '22 21:09

Michael Cramer


To do this, you must do the following. If you don't know what "options" and "arguments" are, read the optparse background.

Each "Command" or "Request" is actually an instance of a model. Define your Request model with all of the parameters someone might provide.

  1. For simple options, you must provide a field with a specific list of CHOICES. For options that are "on" or "off" (-x in the command-line) you should provide a CHOICE list with two human-understandable values ("Do X" and "Do not do X".)

  2. For options with a value, you must provide a field that takes the option's value. You must write a Form with the validation for this field. We'll return to option value validation in a bit.

  3. For arguments, you have a second Model (with an FK to the first). This may be as simple as a single FilePath field, or may be more complex. Again, you may have to provide a Form to validate instances of this Model, also.

Option validation varies by what kind of option it is. You must narrow the acceptable values to be narrowest possible set of characters and write a parser that is absolutely sure of passing only valid characters.

Your options will fall into the same categories as the option types in optparse -- string, int, long, choice, float and complex. Note that int, long, float and complex have validation rules already defined by Django's Models and Forms. Choice is a special kind of string, already supported by Django's Models and Forms.

What's left are "strings". Define the allowed strings. Write a regex for those strings. Validate using the regex. Most of the time, you can never accept quotes (", ' or `) in any form.

Final step. Your Model has a method which emits the command as a sequence of strings all ready for subprocess.Popen.


Edit

This is the backbone of our app. It's so common, we have a single Model with numerous Forms, each for a special batch command that gets run. The Model is pretty generic. The Forms are pretty specific ways to build the Model object. That's the way Django is designed to work, and it helps to fit with Django's well-thought-out design patterns.

Any field that is "available as open text fields" is a mistake. Each field that's "open" must have a regex to specify what is permitted. If you can't formalize a regex, you have to rethink what you're doing.

A field that cannot be constrained with a regex absolutely cannot be a command-line parameter. Period. It must be stored to a file to database column before being used.


Edit

Like this.

class MySubprocessCommandClass( models.Model ):
    myOption_1 = models.CharField( choice = OPTION_1_CHOICES, max_length=2 )
    myOption_2 = models.CharField( max_length=20 )
    etc.
    def theCommand( self ):
        return [ "theCommand", "-p", self.myOption_1, "-r", self.myOption_2, etc. ]

Your form is a ModelForm for this Model.

You don't have to save() the instances of the model. We save them so that we can create a log of precisely what was run.

like image 34
S.Lott Avatar answered Sep 28 '22 21:09

S.Lott


The answer is, don't let users type in shell commands! There is no excuse for allowing arbitrary shell commands to be executed.

Also, if you really must allow users to supply arguments to external commands, don't use a shell. In C, you could use execvp() to supply arguments directly to the command, but with django, I'm not sure how you would do this (but I'm sure there is a way). Of course, you should still do some argument sanitation, especially if the command has the potential to cause any harm.

like image 28
Zifre Avatar answered Sep 24 '22 21:09

Zifre