paramiko.SSHClient().exec_命令的转义参数

2024-06-10 14:12:13 发布

您现在位置:Python中文网/ 问答频道 /正文

为了安全地用作命令行参数,对字符串进行转义的最佳方法是什么?我知道使用subprocess.Popen可以解决这个问题,但是对于paramiko来说,这似乎并不正确。示例:

from subprocess import Popen
Popen(['touch', 'foo;uptime']).wait()

这将创建一个名为foo;uptime的文件,这正是我想要的。比较:

from paramiko import SSHClient()
from subprocess import list2cmdline
ssh = SSHClient()
#... load host keys and connect to a server
stdin, stdout, stderr = ssh.exec_command(list2cmdline(['touch', 'foo;uptime']))
print stdout.read()

这将创建一个名为foo的文件,并打印远程主机的正常运行时间。它已将uptime作为第二个命令执行,而不是将其用作第一个命令touch的参数的一部分。这不是我想要的。

我试着在发送到list2cmdline之前和之后用反斜杠转义分号,但最后得到了一个名为foo\;uptime的文件。

此外,如果使用带有空格的命令而不是uptime,则可以正常工作:

stdin, stdout, stderr = ssh.exec_command(list2cmdline(['touch', 'foo;echo test']))
print stdout.read()

这将创建一个名为foo;echo test的文件,因为list2cmdline用引号将其括起来。

另外,我尝试了pipes.quote(),它的效果与list2cmdline相同。

编辑:为了澄清这一点,我需要确保在远程主机上只执行一个命令,而不管接收到什么样的输入数据,这意味着要转义诸如;&和backtick之类的字符。


Tags: 文件fromimport命令paramiko参数foostdout
3条回答

^{}是我要找的。

re.*escape(**string*)

Return **string* with all non-alphanumerics backslashed...

示例:

from paramiko import SSHClient()
from subprocess import list2cmdline
import re
ssh = SSHClient()
#... load host keys and connect to a server
stdin, stdout, stderr = ssh.exec_command(' '.join(['touch', re.escape('foo;uptime')]))

这将在服务器上创建一个名为foo;uptime的文件,这正是我想要的。

我已经尝试了所有我能想到的shell元字符,它工作了:

stdin, stdout, stderr = ssh.exec_command(' '.join(['touch', re.escape('test;rm foo&echo "Uptime: `uptime`"')]))

使用list2cmdline()没有成功,因为它以Microsoft命令行为目标,该命令行的规则与使用SSH与之通信的POSIX命令行的规则不同。

相反,请使用本机Python例程pipes.quote(),并小心将其分别应用于命令中的每个参数。这将为SSH提供一个工作命令行:

from pipes import quote
command = ['touch', 'foo;uptime']
print ' '.join(quote(s) for s in command)

输出小心地引用第二个参数以保护;字符:

touch 'foo;uptime'

假设远程用户有一个POSIX shell,这应该可以工作:

def shell_escape(arg):
    return "'%s'" % (arg.replace(r"'", r"'\''"), )

为什么这么做?

POSIX shell single quotes定义为:

Enclosing characters in single-quotes ( '' ) shall preserve the literal value of each character within the single-quotes. A single-quote cannot occur within single-quotes.

这里的想法是用单引号将字符串括起来。单凭这一点,几乎就足够了——除了一句引语之外,每个字符都会被逐字解释。对于单引号,从单引号字符串(第一个')中删除,添加一个单引号(第\'),然后恢复单引号字符串(最后一个')。

这有什么用?

这应该适用于任何POSIX shell。我已经用冲刺和猛击测试过了。Solaris 5.10的/bin/sh(我认为它与POSIX不兼容,我找不到它的规范)似乎也可以工作。

对于任意的远程主机,我认为这是不可能的。我认为ssh将使用远程用户的shell(如/etc/passwd中配置的)执行您的命令。如果远程用户可能正在运行,例如/usr/bin/pythongit-shell或其他内容,那么不仅任何引用方案可能会遇到跨外壳的不一致,而且您的命令执行也可能会失败。

csh/tcsh公司

稍微有点问题的是,远程用户可能正在运行tcsh,因为有些人实际上是在野外运行,并且可能希望paramiko的exec_command工作。(作为shell的/usr/bin/python用户可能没有这样的期望…)

tcsh似乎主要起作用。然而,我想不出一种方式来引用一个换行符,这样它就会很高兴。在单引号字符串中包含换行符似乎使tcsh不高兴:

$ tcsh -c $'echo \'foo\nbar\''
Unmatched '.
Unmatched '.

除了换行符,我所尝试的一切似乎都与tcsh一起工作(包括单引号、双引号、反斜杠、嵌入制表符、星号等)。

测试外壳逃逸

如果您有一个转义方案,以下是一些您可能需要测试的内容:

  • 转义序列(\n\t,…)
  • 引号('"\
  • 全局字符(*?[]等)
  • 作业控制和管道(|&||&&,…)
  • 换行符

换行符值得特别注意。^{} solution没有正确处理这个问题——它转义了任何非字母数字字符,POSIX shell认为转义的换行符(即,在Python中,两个字母的字符串"\\\n")是零个字符,而不是一个换行符。我认为re.escape可以正确处理所有其他情况,但使用为正则表达式设计的东西来为shell转义会让我感到害怕。结果可能会成功,但我会担心re.escape或shell转义规则(如换行符)中的细微情况,或者API中未来可能的更改。

您还应该知道转义序列可以在不同的阶段进行处理,这会使测试变得复杂——您只关心shell传递给程序的内容,而不关心程序的功能。使用printf "%s\n" escaped-string-to-test可能是最好的选择。echo的工作异常糟糕:在dash中,echo内置进程反斜杠转义类似于\n。使用/bin/echo通常是安全的,但是在我测试过的Solaris 5.10机器上,它还处理类似\n的序列。

相关问题 更多 >