转义paramiko.SSHClient()。exec_command的参数

时间:2010-07-02 04:29:23

标签: python paramiko

将字符串作为命令行参数安全使用以逃避字符串的最佳方法是什么?我知道使用subprocess.Popen使用list2cmdline()来处理这个问题,但这似乎对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具有相同的效果。

编辑:为了澄清,我需要确保在远程主机上只执行一个命令,无论我收到什么输入数据,这意味着转义像;&这样的字符和反击。

3 个答案:

答案 0 :(得分:10)

假设远程用户有一个POSIX shell,这应该有效:

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

为什么这样做?

POSIX shell single quotes定义为:

  

用单引号('')括起字符应保留单引号中每个字符的字面值。单引号不能出现单引号。

这里的想法是将字符串括在单引号中。仅此一项就足够了 - 除了单引号之外的每个字符都将按字面解释。对于单引号,您将退出单引号字符串(第一个'),添加单引号(\'),然后恢复单引号字符串(最后' })。

这有什么作用?

这适用于任何POSIX shell。我用破折号和bash测试过它。 Solaris 5.10的/bin/sh(我认为它不兼容POSIX,我找不到规范)似乎也有效。

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

csh / tcsh

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

tcsh似乎主要起作用。但是,我无法找到引用换行符的方法,以便它会很开心。在单引号字符串中包含换行符似乎使得tcsh不满意:

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

除了换行符之外,我尝试的所有内容似乎都适用于tcsh(包括单引号,双引号,反斜杠,嵌入式标签,星号等)。

测试shell转义

如果你有一个转义计划,这里有一些你可能想要测试的东西:

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

换行值得特别注意。 re.escape solution没有处理这个权利---它会转义任何非字母数字字符,并且POSIX shell认为转义的换行符(即Python中的双字母字符串"\\\n")为零字符,而不是一个换行符。我认为 re.escape正确处理所有其他情况,但它让我害怕使用为正则表达式设计的东西来逃避shell。它可能会起作用,但我担心re.escape中的一个微妙案例或shell转义规则(如换行符),或API中可能的未来更改。

您还应该知道转义序列可以在各个阶段进行处理,这会使测试变得复杂 - 您只关心shell传递给程序的内容,而不是程序的作用。使用printf "%s\n" escaped-string-to-test可能是最好的选择。 echo效果出奇的很差:在短划线中,echo内置进程反斜杠转义为\n。使用/bin/echo通常是安全的,但在我测试的Solaris 5.10机器上,它还处理\n之类的序列。

答案 1 :(得分:0)

您没有成功使用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'

答案 2 :(得分:-3)

我正在寻找

re.escape()

  

重。的 *逃逸(**字符串*)

     

返回**字符串*,所有非字母数字反映...

示例:

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`"')]))