开发者

Escape arguments for paramiko.SSHClient().exec_command

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.


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 $'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.


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'


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜