1

У меня есть следующий фрагмент:

ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- \
  "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"

который вдохновлен следующим (рабочим) фрагментом:

ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < \
  "./server-root-setup.sh" "${BB_USER}" "${APP_USER}"

где оба файла являются локальными файлами.

Однако я не могу найти способ процитировать или экранировать верхнюю команду, чтобы она успешно работала. Выше приведено ${value of BB_USER}": ./server-user-setup.sh: Permission denied . Я перепробовал много вариантов:


Кто-нибудь может объяснить мне, как правильно процитировать / экранировать эту команду?


Обновление: многообещающее сближение - запускает скрипт, но не передает аргументы!

ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- su www -c < \
  "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"

возвращается

server-user-setup.sh: Wrong number of arguments. # Part of the script - no args passed
su www -c ${value of BB_USER} ${value of APP_USER}    

1 ответ1

1

ТЛ; др

ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" \
  'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"

Внедрение кода возможно через переменные BB_USER и APP_USER .


Длинный ответ

Давайте проанализируем, что происходит. Это довольно сложно. Для ясности предположим:

DO_DROPLET_IP=ip
SSH_PRIVKEY_PATH=key
BB_USER=b-user
APP_USER=a-user

Оригинальный (рабочий) фрагмент

ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < \
    "./server-root-setup.sh" "${BB_USER}" "${APP_USER}"

Все переменные раскрываются локально. Перенаправление локальное, и не важно, что оно посередине. С нашими предполагаемыми значениями переменных это эквивалентная команда:

<"./server-root-setup.sh" ssh root@"ip" -i "key" sh -s -- "b-user" "a-user"

ssh получает следующие аргументы: root@ip , -i , key , sh , -s , -- , b-user , a-user . Так как root@ip выглядит как user@hostname , первый следующий аргумент, не распознанный как option (например, -i) или option-аргумент (например, key является аргументом option для -i), должен запускать массив операндов для построения дистанционная команда от. Указанный аргумент - sh и это команда, которую ssh пытается выполнить на удаленной стороне:

sh -s -- b-user a-user

Это запускает sh который ожидает команды от своего стандартного ввода (из-за -s). -- это соглашение POSIX, которое сообщает инструменту, что все, что следует, не является опцией (см. это, руководство 10). Таким образом, даже если наш ${BB_USER} расширится до -f или около того, sh не посчитает это вариантом. Следующие операнды задаются как позиционные параметры оболочки ($1 , $2). Stdin предоставляется ssh , локальный файл ./server-root-setup.sh в потоковом режиме.

Если файл и две соответствующие переменные были доступны на удаленной стороне, вы могли бы получить (почти) тот же результат с помощью следующей команды:

sh "./server-root-setup.sh" ${BB_USER} ${APP_USER}

Или, если в сценарии есть правильный шебанг:

./server-root-setup.sh ${BB_USER} ${APP_USER}

Таким образом, фрагмент - это действительно способ запустить локальный скрипт на удаленной стороне. Но обратите внимание, я намеренно не цитировал ${BB_USER} или ${APP_USER} . В оригинальном фрагменте они цитируются на локальной стороне, но это не имеет значения, потому что ssh создает и передает команду как строку для анализа на удаленной стороне. Если любая из двух переменных содержит внутренние пробелы, удаленный sh получит больше аргументов, чем вы ожидали; ведущие или конечные пробелы будут потеряны. Такие символы, как $ , " , ' или \ могут вызвать проблемы, если только значения переменных не были специально разработаны для анализа (но тогда их нельзя было использовать локально для получения эквивалентного результата, поэтому я написал" почти ").

Хуже всего будет, если, например, ${APP_USER} расширится до a-user& rogue_command . На удаленной стороне вы получите

sh -s -- b-user a-user& rogue_command

Обратите внимание, что такое внедрение кода не может произойти, когда вы запускаете вещи локально (с кавычками или без них)

./server-root-setup.sh ${BB_USER} ${APP_USER}

потому что разделители как & и ; распознаются до раскрытия переменных, а не после. Но если вы расширяете переменные локально, а затем полученная строка снова анализируется на удаленной стороне, это совсем другая история. Это как удаленный eval.

Я предполагаю, что ${BB_USER} и ${APP_USER} - это имена пользователей, довольно безопасные строки, поэтому все работает, несмотря на возможные общие проблемы. Но если эти переменные не полностью контролируются вами, такой подход небезопасен.


Ваша (неудачная) попытка

ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- \
  "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"

На этот раз перенаправления нет. Снова переменные раскрываются локально, и эквивалентная команда выглядит следующим образом:

ssh root@"ip" -i "key" su www -c sh -c -- "./server-user-setup.sh" "b-user" "a-user"

ssh пытается запустить это на удаленной стороне:

su www -c sh -c -- ./server-user-setup.sh b-user a-user

Обратите внимание, что все аргументы теперь являются аргументами su . В этот момент sh - это просто аргумент-опция для опции -c , аналогично -- это аргумент-опция для другой опции -c (поэтому здесь не указывается конец опции). Мои тесты показывают, что из нескольких параметров -c в su действует только последний. Это означает, что -c sh отбрасывается, su использует любую оболочку, указанную в (удаленном) /etc/passwd для пользователя www . Это может или не может быть sh . Допустим, это some-shell . Это то, что будет дальше (как пользователь www ):

some-shell -c --   # but wait, it's not all

с остальными операндами su , так

some-shell -c -- ./server-user-setup.sh b-user a-user

Если some-shell - это обычная оболочка типа sh или bash , она ведет себя одинаково. Здесь -c не принимает параметр-аргумент (см. Спецификацию), поэтому -- указывает конец параметров. В этом случае следующий аргумент не может быть принят в качестве опции, поэтому мы можем опустить --:

some-shell -c ./server-user-setup.sh b-user a-user

Это как

sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]... command_string [command_name [argument...]]

или (отказываясь от всего, что мы не используем)

some-shell -c command_string command_name argument

Так ./server-user-setup.sh является command_string, b-user command_name и a-user является аргументом.

-c
Чтение команд из операнда command_string . Задайте значение специального параметра 0 […] из значения операнда command_name и позиционных параметров ($1 , $2 и т.д.) В последовательности от оставшихся операндов аргумента. [...]

По сути, удаленная some-shell пытается прочитать (source) ./server-user-setup.sh со строкой a-user доступной как $1 . Специальный параметр 0 (теперь со значением b-user) - это то, что оболочка считает своим собственным именем. Имя используется в сообщениях об ошибках, подобных этому:

b-user: ./server-user-setup.sh: Permission denied

Очевидно, что на удаленной стороне находится ./server-user-setup.sh но пользователь www не может его прочитать.


Мой подход (все еще небезопасно):

ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" \
  'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
# 1                         12                          23 3

Последняя строка (комментарий) просто для перечисления цитат в предыдущей строке. Чтобы понять, как работает команда, нам нужно "расшифровать" это безумное цитирование. Важные вещи:

  • Все в 1 и 3 одинарных кавычках следует понимать буквально.
  • ${BB_USER} и ${APP_USER} в двойные кавычки 2 ; "внутренние" одинарные кавычки относятся к строке в кавычках, они не препятствуют раскрытию переменных.
  • Везде, где закрывающая кавычка непосредственно предшествует открывающей кавычке (например, кавычки выше 12), строки в кавычках будут объединены.

Зная это, мы можем сказать, что команда с расширенными переменными выглядит примерно так:

<"./server-user-setup.sh" ssh root@"ip" -i "key" 'su www -c "exec sh -s -- Xb-userX Xa-userX"'

где X обозначает (только для нас, здесь и сейчас) символ одинарной кавычки, который принадлежит строке, начинающейся с su и заканчивающейся последней " . Извините, я не мог выразить это более ясно. Надеюсь, это будет менее загадочно, когда мы увидим, что получит ssh :

root@ip , -i , key , su www -c "exec sh -s -- 'b-user' 'a-user'"

Последний аргумент содержит двойные кавычки и одинарные кавычки. Именно эта строка ssh будет запускаться на удаленной стороне. Примечание. Я позаботился о том, чтобы передать всю строку как один аргумент в ssh , чтобы инструмент не создавал строку из нескольких аргументов и пробелов; Таким образом, я полностью контролирую строку. В общем, это было бы важно, например, если бы ${BB_USER} расширился до чего-либо с завершающим пробелом (в этом случае исходный фрагмент не получится).

Команда, которая выполняется на удаленной стороне:

su www -c "exec sh -s -- 'b-user' 'a-user'"

Поведение su уже обсуждалось. Обратите внимание, что вся строка в кавычках является опцией-аргументом для -c . Если бы не кавычка, -s был бы вариантом su . Вот что работает от имени пользователя www :

some-shell -c "exec sh -s -- 'b-user' 'a-user'"

Обратите внимание, что кавычки в вызове su также приводят к тому, что нет command_name и argument(ов) (сравните аргумент some-shell -c command_string command_name argument где-то выше). Тогда some-shell просто запускается:

exec sh -s -- 'b-user' 'a-user'

Это будет работать без exec , но, поскольку нам больше не нужна some-shell , exec может быть хорошей идеей. Это заставляет sh заменить some-shell , поэтому вместо some-shell и sh как его потомка остается только sh , как если бы мы запускали some-shell мы имели

sh -s -- 'b-user' 'a-user'

Теперь это очень похоже на исходный фрагмент кода (sh -s -- b-user a-user). Благодаря дополнительным кавычкам, возможные пробелы или несколько других проблемных символов из ${BB_USER} или ${APP_USER} могут ничего не сломать. Однако ' или " будет. Внедрение кода все еще возможно. Рассмотрим расширение ${APP_USER} до "& rogue_command;" , Первое, что нужно запустить на удаленной стороне, будет:

su www -c "exec sh -s -- 'b-user' '"& rogue_command;"'"

Неважно, some-shell выдаст ошибку, rogue_command все равно будет работать. Убедитесь, что ваши переменные расширены до безопасных строк.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .