Также существует вероятность уязвимостей в системе безопасности, использующих уведомления ядра и разветвляющихся, чтобы избежать обнаружения при сканировании таблицы процессов; это, если все сделано правильно, приводит к тому, что ваш процесс имеет более низкий 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.
Я являюсь автором вопроса, и это моя попытка создать скрипт, решающий проблему. Скрипт предназначен для работы на стороне клиента, он заменяет 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
позволяет избежать проблемы. Он должен работать нормально, если соблюдены только технические требования (см. «Требования» ниже ).
Сценарий экспериментальный, и я пытался установить trap
s, чтобы сохранить статус выхода и очистить при выходе в нормальном (? )способ. Я не уверен, что мне это удалось.
Сценарий никогда не задумывался как надежный. Рассматривайте это как доказательство концепции.
Используйте 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
; сценарий создает главные и подчиненные соединения. Удаленные требования (на сервере):
/proc
псевдо -файловая система. /proc/nnnn/fd/N
другого процесса, принадлежащего тому же пользователю. echo
в .bashrc
, с sshe
ситуация аналогична ). Во время тестов я успешно подключился из Kubuntu (18.04.5 LTS )к различным серверам Debian или Debian -. Мои ssh
и sshd
взяты из OpenSSH.
sshe
(если он не решит вернуться к ssh
или кssh -t
)запускает ssh
дважды:
ssh -M … -T …
— это основное соединение, которое не выделяет tty на удаленной стороне. Шелл-код, который он там запускает, сообщает свой PID через stdout и exec
s длинному -работающемуsleep
(около 68 лет ). Стандартные файловые дескрипторы этого процесса будут использоваться другим процессом (es ).
PID, полученный от ведущего ssh
, принимается read
. После этого стандартный вывод мастера ssh
перейдет в фоновый режим cat
, единственной целью которого является передача его на (локальный )стандартный вывод sshe
.
Позднее 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
не взаимодействуют с одним и тем же терминалом одновременно. Это не будет работать со смесью локальных и удаленных sudo
s.
Надеюсь, ваш локальный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
), для этого. Теперь я знаю, что это не так просто; и я подозреваю, что возможно я все еще что-то упускаю.