paramiko.SSHClient().exec_command 的转义参数
转义字符串以安全用作命令行参数的最佳方法是什么?我知道使用 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
具有相同的效果。
编辑:为了澄清,我需要确保在远程主机上只执行一个命令,无论我收到什么输入数据,这意味着转义诸如 ;
、& 等字符。
和反引号。
What is the best way to escape a string for safe usage as a command-line argument? I know that using subprocess.Popen
takes care of this using list2cmdline()
, but that doesn't seem to work correctly for paramiko. Example:
from subprocess import Popen
Popen(['touch', 'foo;uptime']).wait()
This creates a file named literally foo;uptime
, which is what I want. Compare:
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()
This creates a file called foo
and prints the uptime of the remote host. It has executed uptime
as a second command instead of using it as part of the argument to the first command, touch
. This is not what I want.
I tried escaping the semicolon with a backslash before and after sending it to list2cmdline
, but then I ended up with a file called foo\;uptime
.
Also, it works correctly if instead of uptime
, you use a command with a space:
stdin, stdout, stderr = ssh.exec_command(list2cmdline(['touch', 'foo;echo test']))
print stdout.read()
This creates a file literally called foo;echo test
because list2cmdline
surrounded it with quotes.
Also, I tried pipes.quote()
and it had the same effect as list2cmdline
.
EDIT: To clarify, I need to make sure that only a single command gets executed on the remote host, regardless of the whatever input data I receive, which means escaping characters like ;
, &
, and the backtick.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
假设远程用户有 POSIX shell,这应该可以工作:
为什么这可以工作?
POSIX shell 单引号定义为:
这里的想法是用单引号将字符串引起来。仅此一点就足够了——除了单引号之外的每个字符都将按字面解释。对于单引号,您可以删除单引号字符串(第一个
'
),添加单引号(\'
),然后恢复单引号字符串(最后一个'
)。这有什么作用?
这应该适用于任何 POSIX shell。我已经用 dash 和 bash 对其进行了测试。 Solaris 5.10 的
/bin/sh
(我相信它与 POSIX 不兼容,而且我找不到其规范)似乎也可以工作。对于任意远程主机,我相信这是不可能的。我认为 ssh 将使用任何远程用户的 shell 执行您的命令(如在 /etc/passwd 或等效文件中配置的)。如果远程用户可能正在运行,例如 /usr/bin/python 或 git-shell 等,那么任何引用方案都可能会遇到交叉问题。 shell 不一致,但你的命令执行也可能会失败。
csh / tcsh
问题稍微大一点的是远程用户可能正在运行 tcsh,因为有些人实际上确实在野外运行它,并且可能期望 paramiko 的 exec_command 能够工作。 (
/usr/bin/python
作为 shell 的用户可能没有这样的期望......)tcsh 似乎大部分工作。但是,我无法找到一种方法来引用换行符,以便它会高兴。在单引号字符串中包含换行符似乎会让 tcsh 不高兴:
除了换行符之外,我尝试过的所有内容似乎都适用于 tcsh(包括单引号、双引号、反斜杠、嵌入式制表符、星号……)。
测试 shell 转义
如果您有转义方案,您可能需要测试以下一些内容:
\n
,\t
, ...)'
、"
、\
)*
、?
、[]
等)|
、&
、||
、& &
, ...)换行符值得注意。
re.escape
解决方案 不能正确处理这个问题——它会转义任何非字母数字字符,并且 POSIX shell 会考虑转义换行符(即,在 Python 中,两个字母的字符串"\\\n"< /code>) 为零个字符,而不是一个换行符,我认为
re.escape
可以正确处理所有其他情况,尽管它让我害怕使用为常规设计的东西。它可能会起作用,但我担心re.escape
或 shell 转义规则(如换行符)中的微妙情况,或者 API 中未来可能的变化。 。您还应该意识到转义序列可以在各个阶段进行处理,这使测试变得复杂——您只关心 shell 传递给程序的内容,而不关心程序做什么。使用 printf "%s\n" escaped-string-to-test 可能是最好的选择。
echo
的效果出人意料地差:在 dash 中,echo
内置处理反斜杠转义,如\n
。使用/bin/echo
通常是安全的,但在我测试的 Solaris 5.10 机器上,它还可以处理像\n
这样的序列。Assuming the remote user has a POSIX shell, this should work:
Why does this work?
POSIX shell single quotes are defined as:
The idea here is that you enclose the string in single quotes. This, alone, is almost good enough --- every character except a single quote will be interpreted literally. For single quotes, you drop out of the single-quoted string (the first
'
), add a single quote (the\'
), and then resume the single quoted string (the last'
).What does this work with?
This should work for any POSIX shell. I've tested it with dash and bash. Solaris 5.10's
/bin/sh
(which I believe is not POSIX-compatible, and I couldn't find a spec for) also seems to work.For arbitrary remote hosts, I believe this is impossible. I think
ssh
will execute your command with whatever the remote user's shell (as configured in/etc/passwd
or equivalent). If the remote user might be running, say,/usr/bin/python
orgit-shell
or something, not only is any quoting scheme probably going to run into cross-shell inconsistencies, but you command execution is probably going to fail too.csh / tcsh
Slightly more problematic is the possibility that the remote user might be running
tcsh
, since some people actually do run that in the wild and might expect paramiko'sexec_command
to work. (Users of/usr/bin/python
as a shell probably have no such expectations...)tcsh seems to mostly work. However, I can't figure out a way to quote a newline such that it will be happy. Including a newline in single-quoted string seems to make tcsh unhappy:
Other than newlines, everything I've tried seems to work with tcsh (including single quotes, double quotes, backslashes, embedded tabs, asterisks, ...).
Testing shell escaping
If you have an escaping scheme, here are some things you might want to test with:
\n
,\t
, ...)'
,"
,\
)*
,?
,[]
, etc.)|
,&
,||
,&&
, ...)Newlines are worth a special note. The
re.escape
solution doesn't handle this right --- it escapes any non-alphanumeric character, and POSIX shell considers an escaped newline (ie, in Python, the two-letter string"\\\n"
) to be zero characters, not a single newline character. I thinkre.escape
handles all other cases correctly, though it scares me to use something designed for regular expressions to do escaping for shell. It might turn out to work, but I'd worry about a subtle case inre.escape
or shell escaping rules (like newlines), or possible future changes in the API.You should also be aware that escape sequences can get processed at various stages, which complicates testing things --- you only care about what the shell passes to a program, not what the program does. Using
printf "%s\n" escaped-string-to-test
is probably the best bet.echo
works surprisingly poorly: In dash, theecho
built-in processes backslash escapes like\n
. Using/bin/echo
is usually safe, but on a Solaris 5.10 machine I tested on, it also handles sequences like\n
.您无法成功使用
list2cmdline()
,因为它以 Microsoft 命令行为目标,而该命令行与您使用 SSH 进行通信的 POSIX 命令行具有不同的规则。相反,请使用本机 Python 例程
pipes.quote()
,并小心地将其单独应用于命令中的每个参数。这将为您提供 SSH 的工作命令行:输出仔细引用第二个参数以保护
;
字符:You are not having success with
list2cmdline()
because it targets the Microsoft command line, which has different rules than the POSIX command line with which you are communicating using SSH.Instead, use the native Python routine
pipes.quote()
, and be careful to apply it separately to each argument in the command. This will give you a working command line for SSH:The output carefully quotes the second argument to protect the
;
character:re.escape()
是什么我正在寻找。示例:
这会在服务器上创建一个名为
foo;uptime
,这就是我想要的。我已经尝试了所有我能想到的 shell 元字符并且它有效:
re.escape()
is what I am looking for.Example:
This creates a file on the server called
foo;uptime
, which is what I want.I have tried all of the shell meta-characters I can think of and it works :