Ну, вы всегда можете сделать:
#! /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выполняется аутентификация целевого пользователя вместо вызывающего) может иметь немного больше смысла, но это все равно будет означать, что вам нужно знать пароли всех этих парней.