ssh с отдельными stdin, stdout, stderr И tty

Также существует вероятность уязвимостей в системе безопасности, использующих уведомления ядра и разветвляющихся, чтобы избежать обнаружения при сканировании таблицы процессов; это, если все сделано правильно, приводит к тому, что ваш процесс имеет более низкий PID, а инструменты процесса не видят рассматриваемый процесс.

http://cve.circl.lu/cve/CVE-2018-1121

procps-ng, procps is vulnerable to a process hiding through race condition. Since the kernel's proc_pid_readdir() returns PID entries in ascending numeric order, a process occupying a high PID can use inotify events to determine when the process list is being scanned, and fork/exec to obtain a lower PID, thus avoiding enumeration. An unprivileged attacker can hide a process from procps-ng's utilities by exploiting a race condition in reading /proc/PID entries. This vulnerability affects procps and procps-ng up to version 3.3.15, newer versions might be affected also.

3
08.06.2021, 20:42
1 ответ

Скрипт

Я являюсь автором вопроса, и это моя попытка создать скрипт, решающий проблему. Скрипт предназначен для работы на стороне клиента, он заменяет sshв рассматриваемой команде. Это экспериментальный . Я называю это sshe. Это сценарий:

#!/bin/sh -

# the name of the script
me="${0##*/}"

# error handling functions
scream() { printf '%s\n' >&2 "$1"; }
die()    { scream "$2"; exit "$1"; }

# edge cases
[ -e /dev/tty ] || { scream "$me: no tty detected, falling back to regular ssh"
   exec ssh "$@"; }
[ "$#" -lt 2 ] && die 1 "usage: $me [options] [user@]hostname command"

# initialization of variables
redir0=''
redir1=''
redir2=''
tty="/dev/$(ps -p "$$" -o tty=)"

# see what needs to be redirected
exec 7>&1
if [ "$(<&0 tty 2>/dev/null)" != "$tty" ]; then redir0=y; fi
if [ "$(<&7 tty 2>/dev/null)" != "$tty" ]; then redir1=y; fi
if [ "$(<&2 tty 2>/dev/null)" != "$tty" ]; then redir2=y; fi
exec 7>&-

# edge case
[ "$redir0$redir1$redir2" ] || { scream "$me: no redirection detected, falling back to ssh -t"
   exec ssh -t "$@"; }

# command line parsing, extract two last arguments:... host command
z="$#"
n="$z"
for arg do
   if [ "$n" -eq "$z" ]; then
      set --
   fi
   case "$n" in
      1) command="$arg"
   ;;
      2) host="$arg"
   ;;
      *)
      set -- "$@" "$arg"
   esac
   n="$(($n - 1))"
done

# prepare to clean on exit
trap 'status="$?"; rm -r "$tmpd" 2>/dev/null; trap - EXIT; exit "$status"' EXIT HUP INT QUIT PIPE TERM

# temporary directory and socket
tmpd="$(mktemp -d)"
[ "$?" -eq 0 ] || exit 1
sock="$tmpd/sock"

# main pipe: ssh master connection -> background cat
(
[ "$redir0" ] || exec 0</dev/null
# ssh master connection, it will report the remote PID of the remote shell via its stdout
ssh -M -S "$sock" "$@" -T "$host" '</dev/null echo "$$"; exec sleep 2147483647'
) | {

# read the remote PID
IFS= read -r rpid || exit 1

# background process to pass data
exec 6<&0
cat <&6 2>/dev/null &

# move original descriptors out of the way
exec </dev/tty >/dev/tty 6>&-

# prepare remote redirections
if [ "$redir0" ]; then redir0="<&6";  fi
if [ "$redir1" ]; then redir1=">&7";  fi
if [ "$redir2" ]; then redir2="2>&8"; fi

# ssh to run the command, with remote tty
ssh -S "$sock" -t "$host" "
 trap 'status=\"\$?\"; kill $rpid 2>/dev/null; trap - EXIT; exit \"\$status\"' EXIT HUP INT QUIT PIPE TERM
 exec 6</proc/$rpid/fd/0 7>/proc/$rpid/fd/1 8>/proc/$rpid/fd/2 9>/dev/tty $redir0 $redir1 $redir2 || exit 3;
 $command"
}


Общий отказ от ответственности

  • Во многих случаях скрипт работает хорошо. Я не имею в виду, что это происходит случайно. Он не терпит неудачу случайно. Я не имею в виду, что он не может обрабатывать некоторые конкретные данные. Он обрабатывает произвольные данные.

    Случаи, когда он «не работает», возникают только из-за его взаимодействия с локальным tty. Данные (, включая произвольные двоичные данные ), которые передаются по каналам, не содержащим tty, всегда в порядке. Пожалуйста, прочитайте остальную часть этого ответа, особенно раздел «Препятствия и предостережения», чтобы понять проблему и узнать, чего следует избегать.

  • Такая команда:

    <binary_input sshe user@server 'sudo tool' >binary_output 2>error.log
    

    позволяет избежать проблемы. Он должен работать нормально, если соблюдены только технические требования (см. «Требования» ниже ).

  • Сценарий экспериментальный, и я пытался установить traps, чтобы сохранить статус выхода и очистить при выходе в нормальном (? )способ. Я не уверен, что мне это удалось.

  • Сценарий никогда не задумывался как надежный. Рассматривайте это как доказательство концепции.


Использование

Используйте ssheследующим образом:

sshe … [user@]hostname command

где обозначает параметры, которые вы использовали бы, если бы исполняемый файл был ssh. Здесь не нужно ставить ни -t, ни -tt(, ни -T). Сценарий предполагает, что вы хотите использовать tty на удаленной стороне (, в противном случае просто используйтеssh). Сценарий ожидает, что по крайней мере один из локальных stdin, stdout, stderr будет перенаправлен с локального tty. Сценарий вернется к ssh -t, если все подключено к локальному терминалу.

Важные вещи:

  • command— это шелл-код, который вы хотите запустить на сервере. Это должно быть единственным аргументом, самым последним аргументом для sshe. Его нельзя опустить.
  • hostnameили user@hostnameдолжен быть предпоследним аргументом. Его нельзя опустить.

Внутренне сценарий должен знать command, чтобы добавить код впереди. Ему нужно знать [user@]hostname, потому что он использует его дважды. Сценарий просто выбирает последний и предпоследний аргумент соответственно, отсюда и вышеупомянутые ограничения.

Не все действительные вызовы sshможно преобразовать в вызовы sshe, просто заменив sshна sshe. Но я считаю, что любой действительный вызов ssh, который запускает код (, а не порождает интерактивную оболочку ), может быть преобразован в допустимую команду sshe. Пример:

ssh user@server -p 1234 echo foo

следует заменить на:

sshe -p 1234 user@server 'echo foo'

(за исключением того, что вам действительно не нужно ssheв этом случае; это просто пример правильного синтаксиса ). Если вы использовали sshe user@server -p 1234 echo foo, то сценарий примет echoв качестве сервера и fooв качестве команды, потому что он не анализирует свои аргументы, как ssh.

Ниже приведены примеры.


Требования,проблемы переносимости

Местные требования (, где ssheработает):

  • /dev/$(ps -p "$$" -o tty=)считается «настоящим именем» управляющего терминала. Сравните этот вопрос .
  • mktemp -d.
  • sshподдерживающие -Mи -S; сценарий создает главные и подчиненные соединения.

Удаленные требования (на сервере):

  • SSH-сервер, способный обрабатывать основные и подчиненные соединения.
  • /procпсевдо -файловая система.
  • Возможность использования /proc/nnnn/fd/Nдругого процесса, принадлежащего тому же пользователю.
  • POSIX -совместимая оболочка.
  • Скрипты бесшумного запуска (сравнитеSCP не работает при echoв .bashrc, с ssheситуация аналогична ).

Во время тестов я успешно подключился из Kubuntu (18.04.5 LTS )к различным серверам Debian или Debian -. Мои sshи sshdвзяты из OpenSSH.


Операция

sshe(если он не решит вернуться к sshили кssh -t)запускает sshдважды:

  1. ssh -M … -T …— это основное соединение, которое не выделяет tty на удаленной стороне. Шелл-код, который он там запускает, сообщает свой PID через stdout и execs длинному -работающемуsleep(около 68 лет ). Стандартные файловые дескрипторы этого процесса будут использоваться другим процессом (es ).

    PID, полученный от ведущего ssh, принимается read. После этого стандартный вывод мастера sshперейдет в фоновый режим cat, единственной целью которого является передача его на (локальный )стандартный вывод sshe.

  2. Позднее ssh … -t …является ведомым соединением, которое выделяет tty на удаленной стороне. Уже зная удаленный PID от основного соединения, он устанавливает перенаправления, поэтому код, переданный в ssheкак command, может использовать отдельные stdin, stdout,stderr (через главное sshсоединение )и tty (через ведомое sshсоединение )на удаленной стороне. Подчиненный sshне использует ни исходный стандартный ввод, ни стандартный вывод из sshe, вместо этого он использует локальный /dev/tty.

Идея аналогична тому, что этот ответ(уже связан с вопросом ). Код в связанном ответе запускаетssh(неявноssh -T)дважды, чтобы предоставить дополнительные дескрипторы. Мой скрипт запускает ssh -Tи ssh -tдля предоставления стандартных дескрипторов и tty. И он использует функциональность ведущего -ведомого ssh, поэтому он аутентифицирует (, например. запрашивает пароль )один раз.

Если ни один из локальных stdin, stdout, stderr не является локальным tty, тогда данные передаются следующим образом:

  • Локальный стандартный ввод направляется мастеру ssh, никакой другой локальный процесс не читает стандартный ввод сценария. Читая из (удаленных)/proc/nnnn/fd/0удаленных процессов, можно получить доступ к локальному стандартному вводу. Подчиненное соединение sshдобавляет перенаправления к command, поэтому оболочка на удаленной стороне использует /proc/nnnn/fd/0в качестве стандартного ввода.

  • Аналогичным образом оболочка на удаленной стороне использует /proc/nnnn/fd/1в качестве стандартного вывода. Все, что идет туда, выйдет из местного хозяина ssh. Это происходит после того, как мастер sshполучил правильный PID из (удаленного )шелл-кода, который он запускал. PID был использован read, любые последующие данные передаются в исходный стандартный вывод ssheв фоновом режиме cat.

  • Точно так же оболочка на удаленной стороне использует /proc/nnnn/fd/2в качестве своего стандартного вывода. Поток будет поступать непосредственно с локального мастера sshна stderr sshe. Некоторые локальные процессы, порожденные сценарием, используют stderr сценария в качестве своего stderr, поэтому, если вы сделаете sshe … 2>error.log, журнал также будет содержать их сообщения об ошибках. В частности, ожидайте Shared connection to server closed.. Это похоже на ssh -T … 2>error.log, где журнал собирает сообщения от удаленной команды (с )и от самого ssh.Я думаю, что можно сделать вариант sshe, который будет передавать stderr от удаленных команд через канал, связанный с stdout еще одного ssh; в этом случае можно будет отличить удаленный stderr от диагностических сообщений, генерируемых локальными инструментами. Однако скрипт этого не делает.

  • Локальный tty доступен для ведущего ssh(, если ему нужно запросить пароль ), а затем для ведомого ssh. (Откровенно говоря, более локальные инструменты, используемые скриптом, имеют доступ к локальному /dev/tty, они его просто не используют. )Ведомое устройство ssh -tиспользует /dev/ttyв качестве стандартного ввода и вывода. Таким образом, он соединяет локальный и удаленный /dev/tty, несмотря на другие перенаправления (, такие как ssh -t, запущенные в терминале без перенаправлений ). Удаленные процессы, читающие из своего /dev/tty, получат то, что локальное подчиненное устройство sshчитает из локального /dev/tty. Удаленные процессы, записывающие в свой /dev/tty, заставят локальное подчиненное устройство sshзаписывать в локальный /dev/tty.

Если локальный stdin, stdout или stderr является локальным tty, то его соответствующий аналог на удаленной стороне (для command, запускаемый удаленно подчиненным устройством ssh), не будет перенаправлен на /proc/nnnn/fd/Nи останется подключен к удаленному tty. В любом случае он попадет на местный телетайп. Дело в том, что он не должен обходить удаленный tty. Причина этого будет ясна через мгновение.

Существует несколько локальных и удаленных перенаправлений, которые не обязательно требуются для работы sshe. Это из-за моих других экспериментальных вещей. Я решил оставить лишние перенаправления, на случай, если они важнее для единственного sshe, чем я помню.


Препятствия и предостережения

Вся концепция не так проста, как может показаться. Терминал может обрабатывать то, что вы вводите (, например. перевести ^Mв^J)и то, что будет напечатано (например. если я catфайл с *окончанием строки nix на терминал,каждый символ новой строки будет работать как возврат каретки _+ новая строка ). Вызовите stty -a, чтобы увидеть множество настроек.

Вот почему вам не нужен tty при обработке произвольных двоичных данных. И вы хотите этого при взаимодействии.

Процессы могут настроить tty так, чтобы он соответствовал их потребностям. См.сырые и приготовленные.

Когда вы sshтаким образом выделяете tty на сервере, процессы там будут видеть его как свой tty. Если им нужно настроить свой tty, они настроят tty, который видят на сервере. У них нет возможности напрямую настроить локальный терминал ssh. Все (? )"готовка" выполняется удаленным tty и sshнастраивает локальный tty так, чтобы он не мешал.

По этой причине ssheне следует обходить удаленный tty при перенаправлении удаленных дескрипторов, которые в конечном итоге должны быть подключены к локальному tty. Если удаленный tty обойден, то не будет никакой сущности, которая могла бы «подготовить» поток. Подключив stdin, stdout или stderr из ssheк локальному tty, вы указываете, что хотите его «приготовить». Это делает ssheпохожим на ssh … command, запускаемым в интерактивной оболочке, т. е. случай, когда все стандартные потоки "готовятся" локальным tty. (обратите внимание, что sshподобен ssh -Tи не локальный терминал в «сыром» режиме ).

Итак, sshe"готовит" то, что, очевидно, вы хотите "приготовить". Проблема возникает, когда вы делаете что-то подобное:

sshe … command | whatever

Данные будут передаваться с удаленного commandна локальный whateverбез «обработки» (, как можно было бы ожидать от ssh … command | whatever), но выходные данные whateverне будут «обрабатываться» локально. ssheможет переконфигурировать локальный tty так, чтобы он «готовил», но если commandпечатает на свой tty (, то есть на удаленный tty, который может или не может «готовить», в зависимости от его настроек )тогда локальный tty не должен «готовить».

ssheне пытается решить эту проблему.В основном он предназначен для поддержки случаев, когда окончательный вывод идет куда-то еще, кроме терминала (, например. в обычный файл или на блочное устройство ). В этом вопросе лучше подходит следующий код:

sshe … command | whatever >some_file

хотя stderr из whateverне будет "приготовлен". Ожидайте, что диагностические сообщения будут выглядеть странно. Обратите внимание, что вы можете перенаправить их в файл (или на другой локальный tty, который их «приготовит» ).

На стороне ввода еще хуже. Если другой локальный процесс попытается прочитать с локального tty, он не только прочитает необработанные данные, но и будет конкурировать с ssheза ввод. Это общая проблема двух процессов, читающих с одного и того же терминала.

Подводя итог, :создайте локальный командный (конвейер ), так что только (один)ssheхочет читать с локального tty; не позволяйте другим инструментам, кроме sshe, печатать на локальный tty, если только вы не переносите «сырой» вывод.

Я разработал sshe, чтобы иметь возможность передавать или обрабатывать двоичные данные. В моем случае вряд ли когда-либо возникнет необходимость читать данные с локального tty или записывать данные на локальный tty. Я могу выносить диагностические сообщения от недостаточно "прожаренных" локальных инструментов. Взамен ssheпозволяет мне использовать удаленный sudo, как если бы он был локальным.


Примеры

  • Чтение или запись на удаленное блочное устройство, которому требуется sudoдоступ.

    • Чтение:

      sshe user@server 'sudo cat /dev/sdx1' >local_file
      # or
      sshe user@server 'sudo pv  /dev/sdx1' >local_file
      
    • Письмо:

      <local_file sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
      

      В моих тестах local pvявно не возражает против того, чтобы локальный tty был «сырым»; или, скорее, конфигурация, которую он налагает, и конфигурация, налагаемая sshe, не являются (? )противоречиво и это не (? )Независимо от того, какой инструмент настраивает локальный терминал первым. Похоже, это работает:

      pv local_file | sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
      

      Обратите внимание, что если настройки из pvнарушены sshe, возможно, вы не сможете ввести пароль на пульт sudo.Если настройки из ssheнарушают pv, то то, что pvвыводит на терминал, может выглядеть искаженным. Даже в этих гипотетических случаях содержимое local_fileдословно доберется до удаленного /dev/sdx1.

  • Локальный sudoи удаленный sudoвместе.

    Если локальный sudoбудет запрашивать ваш пароль, sudo … | sshe … 'sudo …'или sshe … 'sudo …' | sudo …не являются хорошей идеей, потому что локальные sudoи ssheбудут читать с локального tty одновременно. Полностью локальный sudo … | sudo …работает, потому что sudoреализует механизм блокировки, поэтому два локальных sudoне взаимодействуют с одним и тем же терминалом одновременно. Это не будет работать со смесью локальных и удаленных sudos.

    Надеюсь, ваш локальныйsudoдопускает тайм-аут . Если это так, вызовите sudo -vзаранее, чтобы предоставить локальный пароль (, если необходимо, )без помех; тогда иди с трубкой:

    • Копирование с удаленного устройства на локальное:

      sudo -v   # input local password if needed
      sshe user@server 'sudo cat /dev/sdx1' | sudo tee /dev/sdy1 >/dev/null
      
    • Копирование с локального устройства на удаленное:

      sudo -v   # input local password if needed
      sudo cat /dev/sdy1 | sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
      
  • Именно то, что требовал вопрос.

    • Сsudo:

      <binary_input sshe user@server 'sudo tool' >binary_output 2>error.log
      
    • Или в более общем смысле:

      <binary_input sshe user@server 'requires-tty' >binary_output 2>error.log
      

Последнее примечание

Раньше я думал, что туннелирование stdin в stdin, stdout в stdout, stderr в stderr и/dev/ttyв /dev/ttyтривиально. Раньше я удивлялся, почему sshне предоставляет опцию (, аналогичную -t), для этого. Теперь я знаю, что это не так просто; и я подозреваю, что возможно я все еще что-то упускаю.

1
28.07.2021, 11:26

Теги

Похожие вопросы