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

发布于 2024-09-08 02:48:38 字数 1227 浏览 7 评论 0原文

转义字符串以安全用作命令行参数的最佳方法是什么?我知道使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

影子的影子 2024-09-15 02:48:38

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

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

为什么这可以工作?

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 -c 

除了换行符之外,我尝试过的所有内容似乎都适用于 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 这样的序列。

echo \'foo\nbar\'' Unmatched '. Unmatched '.

除了换行符之外,我尝试过的所有内容似乎都适用于 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:

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

Why does this work?

POSIX shell single quotes are defined as:

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.

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 or git-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's exec_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:

$ tcsh -c 

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:

  • Escape sequences (\n, \t, ...)
  • Quotes (', ", \)
  • Globbing characters (*, ?, [], etc.)
  • Job control and pipelines (|, &, ||, &&, ...)
  • Newlines

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 think re.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 in re.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, the echo 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.

echo \'foo\nbar\'' Unmatched '. Unmatched '.

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:

  • Escape sequences (\n, \t, ...)
  • Quotes (', ", \)
  • Globbing characters (*, ?, [], etc.)
  • Job control and pipelines (|, &, ||, &&, ...)
  • Newlines

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 think re.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 in re.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, the echo 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.

荆棘i 2024-09-15 02:48:38

您无法成功使用 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'

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:

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

The output carefully quotes the second argument to protect the ; character:

touch 'foo;uptime'
忆离笙 2024-09-15 02:48:38

re.escape() 是什么我正在寻找。

re.*转义(**字符串*)

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

示例:

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

re.escape() is what I am looking for.

re.*escape(**string*)

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

Example:

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

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 :

stdin, stdout, stderr = ssh.exec_command(' '.join(['touch', re.escape('test;rm foo&echo "Uptime: `uptime`"')]))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文