ossa/doc/source/guidelines/dg_use-subprocess-securely.rst

87 lines
3.1 KiB
ReStructuredText

.. :Copyright: 2015, OpenStack Foundation
.. :License: This work is licensed under a Creative Commons
Attribution 3.0 Unported License.
http://creativecommons.org/licenses/by/3.0/legalcode
Use subprocess securely
=======================
Many common tasks involve interacting with the operating system - we write a
lot of code that configures, modifies, or otherwise controls the system, and
there are a number of pitfalls that can come along with that.
Shelling out to another program is a pretty common thing to want to do. In most
cases, you will want to pass parameters to this other program. Here is a simple
function for pinging another server.
Incorrect
~~~~~~~~~
.. code:: python
def ping(myserver):
return subprocess.check_output('ping -c 1 %s' % myserver, shell=True)
>>> ping('8.8.8.8')
64 bytes from 8.8.8.8: icmp_seq=1 ttl=58 time=5.82 ms
This program just supplies a string as a command to the shell, which runs it
without thinking too hard about it. There's no semantic separation between the
input parameters, i.e. the shell cannot tell where the command is supposed to
end, and where the parameters start.
If the ``myserver`` parameter is user controlled, this can be used to execute
arbitrary programs, such as rm:
.. code:: python
>>> ping('8.8.8.8; rm -rf /')
64 bytes from 8.8.8.8: icmp_seq=1 ttl=58 time=6.32 ms
rm: cannot remove `/bin/dbus-daemon': Permission denied
rm: cannot remove `/bin/dbus-uuidgen': Permission denied
rm: cannot remove `/bin/dbus-cleanup-sockets': Permission denied
rm: cannot remove `/bin/cgroups-mount': Permission denied
rm: cannot remove `/bin/cgroups-umount': Permission denied
...
If you choose to test this, we recommend that you pick a command that is less
destructive than 'rm -rf /', such as 'touch helloworld.txt'.
Correct
~~~~~~~
This function can be re-written safely:
.. code:: python
def ping(myserver):
args = ['ping', '-c', '1', myserver]
return subprocess.check_output(args, shell=False)
Rather than passing a string to subprocess, our function passes a list of
strings. The ping program gets each argument separately (even if the argument
has a space in it), so the shell does not process other commands that are
provided by the user after the ping command terminates. You do not have to
explicitly set shell=False - it is the default.
If we test this with the same input as before, the ping command interprets the
``myserver`` value correctly as a single argument, and complains because that
is a really weird hostname to try and ping.
.. code:: python
>>> ping('8.8.8.8; rm -rf /')
ping: unknown host 8.8.8.8; rm -rf /
This program is now much safer, even if it has to allow user-provided input.
Consequences
~~~~~~~~~~~~
- If you use shell=True, your code is extremely likely to be vulnerable
- Even if *your* code is not vulnerable, the next person who maintains
can easily introduce a vulnerability.
- Shell injections are arbitrary code execution - a competent attacker
will use these to compromise the rest of your system.