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.