我用子进程模块调用不同的进程。然而,我有一个问题。

在以下代码中:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

and

callProcess = subprocess.Popen(['ls', '-l']) # without shell

这两个工作。在阅读了文档之后,我知道shell=True意味着通过shell执行代码。也就是说,如果不存在,这个过程将直接启动。

那么对于我的情况,我应该选择什么呢?我需要运行一个进程并获得它的输出。从壳内或者壳外调用有什么好处呢?


当前回答

这里展示了一个Shell=True可能出错的示例

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

查看这里的文档:

其他回答

不通过shell调用的好处是,您不会调用一个“神秘程序”。在POSIX上,环境变量SHELL控制作为“SHELL”调用的二进制文件。在Windows上,没有bourne shell的后代,只有cmd.exe。

因此调用shell调用用户选择的程序,并且依赖于平台。一般来说,避免通过shell调用。

通过shell调用确实允许您根据shell的通常机制展开环境变量和文件glob。在POSIX系统上,shell将文件glob扩展为一个文件列表。在Windows上,一个文件glob(例如,“*.*”)不会被shell扩展(但是命令行上的环境变量会被cmd.exe扩展)。

如果您想要环境变量扩展和文件glob,请研究1992-ish对通过shell执行子程序调用的网络服务的ILS攻击。示例包括涉及ILS的各种sendmail后门。

总之,使用shell=False。

让我们假设您使用shell=False并以列表的形式提供命令。一些恶意用户试图注入'rm'命令。 你会看到,rm会被解释为一个参数而ls会试图找到一个叫rm的文件

>>> subprocess.run(['ls','-ld','/home','rm','/etc/passwd'])
ls: rm: No such file or directory
-rw-r--r--    1 root     root          1172 May 28  2020 /etc/passwd
drwxr-xr-x    2 root     root          4096 May 29  2020 /home
CompletedProcess(args=['ls', '-ld', '/home', 'rm', '/etc/passwd'], returncode=1)

shell=False在默认情况下不是安全的,如果你没有正确地控制输入。你仍然可以执行危险的命令。

>>> subprocess.run(['rm','-rf','/home'])
CompletedProcess(args=['rm', '-rf', '/home'], returncode=0)
>>> subprocess.run(['ls','-ld','/home'])
ls: /home: No such file or directory
CompletedProcess(args=['ls', '-ld', '/home'], returncode=1)
>>>

我在容器环境中编写我的大部分应用程序,我知道哪个shell正在被调用,我不接受任何用户输入。

所以在我的用例中,我没有看到安全风险。而且,创建长串命令要容易得多。希望我没说错。

通过shell执行程序意味着传递给程序的所有用户输入都将根据所调用shell的语法和语义规则进行解释。在最好的情况下,这只会给用户带来不便,因为用户必须遵守这些规则。例如,包含特殊shell字符(如引号或空格)的路径必须转义。在最坏的情况下,它会导致安全泄漏,因为用户可以执行任意程序。

shell=True有时可以方便地使用特定的shell特性,如分词或参数展开。然而,如果需要这样的特性,请使用提供给您的其他模块(例如os.path.expandvars()用于参数展开或shlex用于单词分割)。这意味着更多的工作,但避免了其他问题。

简而言之:无论如何要避免shell=True。

>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

将shell参数设置为真值会导致子进程生成一个中间shell进程,并告诉它运行该命令。换句话说,使用中间shell意味着在运行命令之前处理命令字符串中的变量、glob模式和其他特殊shell特性。在本例中,$HOME在echo命令之前被处理。实际上,这就是shell扩展命令的情况,而ls -l命令被认为是一个简单的命令。

源:子进程模块

这里展示了一个Shell=True可能出错的示例

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

查看这里的文档: