Escape-аргументы для paramiko.SSHClient (). Exec_command - PullRequest
3 голосов
/ 02 июля 2010

Каков наилучший способ избежать строки для безопасного использования в качестве аргумента командной строки? Я знаю, что использование 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.

EDIT: чтобы уточнить, мне нужно убедиться, что на удаленном хосте выполняется только одна команда, независимо от того, какие входные данные я получаю, что означает экранирование таких символов, как ;, & и обратный удар. .

Ответы [ 3 ]

10 голосов
/ 09 декабря 2012

Если у удаленного пользователя есть оболочка POSIX, это должно работать:

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

Почему это работает?

Одиночные кавычки оболочки POSIX определяются как:

Заключение символов в одинарные кавычки ('') должно сохранять буквальное значение каждого символа в одинарных кавычках. Одиночная кавычка не может встречаться в одинарных кавычках.

Идея в том, что вы заключаете строку в одинарные кавычки. Это само по себе почти достаточно хорошо - каждый символ, кроме одной кавычки, будет интерпретироваться буквально. Для одинарных кавычек вы выпадаете из строки в одинарных кавычках (первая '), добавляете одинарные кавычки (\') и затем возобновляете строку в одинарных кавычках (последняя ').

С чем это работает?

Это должно работать для любой оболочки POSIX. Я проверил это с тире и Баш. * Solaris 5.10 /bin/sh (который, я считаю, не совместим с POSIX, и я не смог найти спецификацию для него) также, похоже, работает.

Для произвольных удаленных хостов я считаю, что это невозможно. Я думаю, что ssh выполнит вашу команду с любой оболочкой удаленного пользователя (как настроено в /etc/passwd или эквивалентной). Если удаленный пользователь может работать, скажем, /usr/bin/python или git-shell или что-то в этом роде, любая схема цитирования может не только столкнуться с несоответствиями между оболочками, но и выполнение вашей команды также может привести к сбою.

csh / tcsh

Чуть более проблематична вероятность того, что удаленный пользователь может запустить tcsh, так как некоторые люди действительно запускают это в дикой природе и могут ожидать, что exec_command парамико будет работать. (Пользователи /usr/bin/python в качестве оболочки, вероятно, не имеют таких ожиданий ...)

tcsh, кажется, в основном работает. Тем не менее, я не могу найти способ процитировать новую строку, чтобы он был счастлив. Включение новой строки в строку в одинарных кавычках делает tcsh несчастным:

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

Кроме новых строк, все, что я пробовал, похоже, работает с tcsh (включая одинарные, двойные, обратные слэши, встроенные табуляции, звездочки, ...).

Проверка оболочки на выход

Если у вас есть схема побега, вот некоторые вещи, с которыми вы можете проверить:

  • Escape-последовательности (\n, \t, ...)
  • Котировки (', ", \)
  • Глобус символов (*, ?, [] и т. Д.)
  • Работа контроля и трубопроводов (|, &, ||, &&, ...)
  • Newlines

Новые строки заслуживают особого внимания. Решение re.escape не обрабатывает это право - оно экранирует любой не алфавитно-цифровой символ, а оболочка POSIX считает экранированную новую строку (т. Е. В Python двухбуквенная строка "\\\n") быть нулем символов, а не один символ новой строки. Я думаю, re.escape правильно обрабатывает все другие случаи, хотя меня пугает использование чего-то, предназначенного для регулярных выражений, для экранирования оболочки. Это может сработать, но я бы беспокоился о тонком случае в re.escape или правилах экранирования оболочки (таких как переводы строк) или возможных будущих изменениях в API.

Вы также должны знать, что escape-последовательности могут обрабатываться на разных этапах, что усложняет тестирование - вам важно только то, что оболочка передает программе, а не то, что программа делает. Использование printf "%s\n" escaped-string-to-test, вероятно, лучшая ставка. echo работает на удивление плохо: в dash обратная косая черта встроенных процессов echo экранируется как \n. Использование /bin/echo обычно безопасно, но на машине с Solaris 5.10, на которой я тестировал, оно также обрабатывает последовательности вроде \n.

0 голосов
/ 31 июля 2016

Вы не добились успеха с list2cmdline(), потому что он нацелен на командную строку Microsoft, которая имеет правила, отличные от командной строки POSIX, с которой вы общаетесь по SSH.

Вместо этого используйте встроенную процедуру Pythonpipes.quote(), и будьте осторожны, чтобы применять его отдельно к каждому аргументу в команде.Это даст вам рабочую командную строку для SSH:

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

Выходные данные тщательно цитируют второй аргумент для защиты символа ;:

touch 'foo;uptime'
0 голосов
/ 07 июля 2010

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, что я и хочу.

Я перепробовал все метасимволы оболочки, о которых могу думать, и это работает:

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