Ну, вы всегда можете сделать:
#! /bin/bash -
{ shopt -s expand_aliases;SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";};alias skip=":||:<<'SWITCH_TO_USER $_u'"
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
${_u+:} alias skip=:;} 2>/dev/null
skip
echo test
a=foo
set a b
SWITCH_TO_USER root
echo "$a and $1 as $(id -un)"
set -x
foo() { echo "bar as $(id -un)"; }
SWITCH_TO_USER rag
foo
set +x
SWITCH_TO_USER root again
echo "hi again from $(id -un)"
(ʘ‿ʘ)
Это сначала началось как шутка, поскольку в нем реализовано то, что запрошено, хотя, вероятно, не совсем так, как ожидалось, и практически не полезно. Но так как он развился до чего-то, что работает в некоторой степени и включает в себя несколько хороших хаков, вот небольшое объяснение:
Как сказал Мирослав , если оставить в стороне Linux стиле возможностей (которые не очень помогают здесь ни в любом случае), единственный способ для непривилегированного процесса изменения UID является выполнение Setuid исполняемого файла.
Однако, как только вы получите привилегию суперпользователя (выполняя исполняемый файл setuid, владельцем которого является, например, root), вы можете переключать эффективный идентификатор пользователя назад и вперед между вашим исходным идентификатором пользователя, 0 и любым другим идентификатором, если только вы не откажетесь от сохраненного идентификатора установленного пользователя ( люблю такие вещи как sudo
или su
обычно делаю).
Например:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Теперь у меня есть env
команда, которая позволяет мне выполнять любую команду с эффективным идентификатором пользователя и сохраненным установленным идентификатором пользователя 0 (мой реальный идентификатор пользователя все еще равен 1000):
$ ./env id -u
0
$ ./env id -ru
1000
$ ./env -u PATH =perl -e '$>=1; system("id -u"); $>=0;$>=2; system("id -u");
$>=0; $>=$<=3; system("id -ru; id -u"); $>=0;$<=$>=4; system("id -ru; id -u")'
1
2
3
3
4
4
perl
имеет обертки к setuid
/ seteuid
(те $>
и $<
переменные).
Так же как и Zsh:
$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2
Хотя выше эти id
команды вызываются с реальным идентификатором пользователя и сохраненным установленным идентификатором пользователя, равным 0 (хотя, если бы я использовал мой ./env
вместо этого sudo
, был бы только сохраненный набор идентификаторов пользователей, в то время как реальный идентификатор пользователя остался бы 1000), что означает, что если бы они были ненадежными командами, они все равно могли бы нанести какой-то ущерб, так что вы бы хотели написать это так:
$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'
(это устанавливает все uids (эффективный, реальный и сохраненный набор) только для выполнения этих команд.
bash
нет такого способа изменить идентификаторы пользователя. Таким образом, даже если у вас есть исполняемый файл setuid, с помощью которого можно вызвать bash
скрипт, это не поможет.
С помощью bash
вам остается выполнять исполняемый файл setuid каждый раз, когда вы хотите изменить uid.
Идея в приведенном выше сценарии заключается в вызове SWITCH_TO_USER, чтобы выполнить новый экземпляр bash для выполнения оставшейся части сценария.
SWITCH_TO_USER someuser
более или менее функция, которая выполняет сценарий снова как другой пользователь (используя sudo
), но пропускает начало сценария до SWITCH_TO_USER someuser
.
Сложность в том, что мы хотим сохранить состояние текущего bash после запуска нового bash от имени другого пользователя.
Давайте разберемся с этим:
{ shopt -s expand_aliases;
Нам понадобятся псевдонимы. Один из трюков в этом скрипте - пропустить часть скрипта, пока что- SWITCH_TO_USER someuser
то вроде:
:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Эта форма похожа на #if 0
используемую в C, это способ полностью закомментировать некоторый код.
:
это неоперация, которая возвращает истину. Итак : || :
, второе :
никогда не выполняется. Тем не менее, он анализируется. И << 'xxx'
это форма здесь-документа, где (потому что xxx
цитируется), не делается никакого расширения или интерпретации.
Мы могли бы сделать:
: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Но это означало бы, что этот документ должен был быть написан и передан как stdin :
. :||:
избегает этого
Теперь мы можем использовать тот факт, что мы bash
очень рано расширяем псевдонимы в процессе анализа. Иметь skip
псевдоним для :||: << 'SWITCH_TO_USER someuther'
части закомментирующей конструкции.
Давайте продолжим:
SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";}
Вот определение функции SWITCH_TO_USER . Ниже мы увидим, что SWITCH_TO_USER в конечном итоге будет псевдонимом, обернутым вокруг этой функции.
Эта функция выполняет основную часть повторного выполнения сценария. В конце мы видим, что он повторно выполняется (в том же процессе из-за exec
) bash
с _x
переменной в его среде (мы используем env
здесь, потому что sudo
обычно дезинфицирует свою среду и не позволяет передавать произвольные переменные через). Это bash
оценивает содержимое этой $_x
переменной как код bash и создает сам скрипт.
_x
определяется ранее как:
_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'
Все из declare
, alias
, shopt -p
set +o
выход составляет дамп внутреннего состояния оболочки. Таким образом, они сбрасывают определение всех переменных, функций, псевдонимов и опций как готовый к проверке код оболочки. Кроме того, мы добавляем настройку позиционных параметров ( $1
, $2
...) на основе значения $_a
массива (см. Ниже), а некоторые очищают, чтобы огромная $_x
переменная не оставалась в environemnt для оставшихся сценария.
Вы заметите, что первая часть до set +x
обернута в группу команд, чей stderr перенаправлен в /dev/null
( {...} 2> /dev/null
). Это потому, что если в какой-то момент сценария set -x
(или set -o xtrace
) выполняется, мы не хотим, чтобы эта преамбула генерировала трассировки, поскольку мы хотим сделать ее как можно менее навязчивой. Таким образом, мы запускаем set +x
(предварительно убедившись, что предварительно сбросили параметры (включая xtrace
)), где трассировки отправляются в / dev / null.
eval "$_X"
STDERR также перенаправлены / DEV / нуль по тем же причинам , но и избежать ошибок , о написании попытки специальных переменных только для чтения.
Давайте продолжим со сценарием:
alias skip=":||:<<'SWITCH_TO_USER $_u'"
Это наш трюк, описанный выше. При первоначальном вызове сценария он будет отменен (см. Ниже).
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
Теперь обертка псевдонимов вокруг SWITCH_TO_USER. Основная причина в том, чтобы иметь возможность передавать позиционные параметры ( $1
, $2
...) в новый, bash
который будет интерпретировать остальную часть скрипта. Мы не могли сделать это в SWITCH_TO_USER
функции, потому что внутри функции "$@"
есть аргументы функций, а не скрипты. Перенаправление stderr в / dev / null снова позволяет скрыть xtraces, а eval
для устранения ошибки bash
. Затем мы вызываем SWITCH_TO_USER
функцию .
${_u+:} alias skip=:
Эта часть отменяет skip
псевдоним (заменяет его командой :
no-op), если $_u
переменная не установлена.
skip
Это наш skip
псевдоним. При первом вызове это будет просто :
(без операции). На подпоследовательности повторных вызовов, то это будет что - то вроде: :||: << 'SWITCH_TO_USER root'
.
echo test
a=foo
set a b
SWITCH_TO_USER root
Итак, здесь, в качестве примера, в этот момент мы повторно вызываем скрипт как root
пользователь, и скрипт восстанавливает сохраненное состояние, переходит к этой SWITCH_TO_USER root
строке и продолжает.
Это означает, что она должна быть написана в точности как stat, с SWITCH_TO_USER
началом строки и с одним пробелом между аргументами.
Большая часть состояния, stdin, stdout и stderr будет сохранена, но не остальные файловые дескрипторы, потому что sudo
обычно их закрывает, если явно не настроено. Так, например:
exec 3> some-file
SWITCH_TO_USER bob
echo test >&3
как правило, не будет работать.
Также обратите внимание, что если вы делаете:
SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root
Это работает только в том случае, если у вас есть право на « sudo
как» alice
и alice
« sudo
как» bob
и « bob
как» root
.
Так что на практике это не очень полезно. Использование su
вместо sudo
(или sudo
конфигурации, где sudo
выполняется аутентификация целевого пользователя вместо вызывающего) может иметь немного больше смысла, но это все равно будет означать, что вам нужно знать пароли всех этих парней.