Shell Injection Vulnerability in Python Subprocess module

If someone wants to start a new process in python subprocess is the module to go. on the subprocess documentation page you must have seen the warning

Although this is set to shell=False as default, unfortunately most of the example there use this as True, And who has the time to read the documentation so we tend to just copy paste the example code. Lets first understand what this option really does and why it should be avoided.

shell=True spawns a new shell and then execute the command inside it. Because of that it will do shell expansion and variable substitution. On unix its equivalent to following shell command

\bin\sh -c $arg[0] $arg[1] ... $arg[n]

i.e All arguments becomes argument to \bin\sh.
Where as shell=False is equivalent to

$arg[0] $arg[1] ... $arg[n]

i.e. First argument becomes the command and the all others are argument to that command.
Lets take a following example

import subprocess as p
p.call("touch ab`id>asdf`.txt", shell=True)

Now on the shell

$ls a*
ab.txt	asdf
$ cat asdf
uid=0(root) gid=0(root) groups=0(root)

Whoooa! We got two files here one is ab.txt and other is asdf instead if ab`id>asdf`.txt. And asdf contains output of command id
The command id>asdf got executed. Now what if the filename was being taken from some external input like a website or desktop application and passed to it. Anyone can disguise a shell script in a filename and get it executed on to the system.

Now lets try to the same thing again with shell=False

import subprocess as p
p.call("touch 'ab`id>asdf`.txt'", shell=False)

The above command will fail with error "No such file or directory" command as the first argument should be the command and rest should be the argument. So we will have to call it like

import subprocess as p
p.call(['touch', 'ab`id>asdf`.txt'], shell=False)

On shell

$ ls a*
ab`id>asdf`.txt

Bingo! The injected command did not got executed, Instead the file got created as the same name as the specified in argument..

Unfortunately if your code already has shell=True you can’t just do a search replace with shell=False.  Its not a drop in replacement. as we have seen earlier in the example. You might have to change the way arguments are being passed

Also os.system and eval also posses the same vulnerability so avoid using that too.

Other programming language like node.js, ruby will also have the similar concepts. So when executing system commands ensure to choose the write option which does not spawn a shell.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.