Почему мы не можем выполнить список команд от имени другого пользователя без sudo?

8

Этот вопрос задавался по-другому на других форумах. Но не было достойного объяснения, почему вы не можете сделать следующее в bash.

#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3

Обычно предлагаемый способ

#!/bin/bash
command1
sudo -u rag command2
sudo -u rag command3

но почему невозможно в bashкакой-то момент переключиться на другого пользователя во время выполнения сценария bash и выполнить остальные команды от имени другого пользователя?

тряпка
источник
Этот вопрос также задавался Superuser по адресу superuser.com/questions/93385/… , и этот ответ superuser.com/a/573882/76251 предоставляет HERE DOC-подобный метод для изменения пользователей в скрипте.
Тим Кеннеди

Ответы:

4

Информация уже есть в моем другом ответе , но она там немного похоронена. Так что я подумал, я бы добавил это сюда.

bashне предусматривает смену пользователей, но zshимеет.

В zsh, вы меняете пользователей, присваивая значения этим переменным:

  • $EUID: изменить эффективный идентификатор пользователя (и ничего больше). Обычно вы можете изменить свой euid между своим реальным идентификатором пользователя и сохраненным идентификатором пользователя (если он вызывается из исполняемого файла setuid) или изменить его на что угодно, если ваш euid равен 0.
  • $UID: измените эффективный идентификатор пользователя, реальный идентификатор пользователя и сохраненный набор идентификатора пользователя на новое значение. Если это новое значение не равно 0, возврата назад не будет, так как после того, как все 3 установлены на одно и то же значение, невозможно изменить его на что-либо еще.
  • $EGIDи $GID: то же самое, но для идентификаторов группы.
  • $USERNAME, Это похоже на использование sudoили su. Он устанавливает ваш euid, ruid, ssuid в uid этого пользователя. Он также устанавливает egid, rgid и ssgid и дополнительные группы на основе членства в группах, как определено в базе данных пользователей. Как и для $UID, если вы установите $USERNAMEна root, нет возвращаться, но , как и для $UID, вы можете изменить пользователь только для субоболочки.

Если вы запускаете эти скрипты как «root»:

#! /bin/zsh -
UID=0 # make sure all our uids are 0

id -u # run a command as root

EUID=1000

id -u # run a command as uid 1000 (but the real user id is still 0
      # so that command would be able to change its euid to that.
      # As for the gids, we only have those we had initially, so 
      # if started as "sudo the-script", only the groups root is a
      # member of.

EUID=0 # we're allowed to do that because our ruid is 0. We need to do
       # that because as a non-priviledged user, we can't set our euid
       # to anything else.

EUID=1001 # now we can change our euid since we're superuser again.

id -u # same as above

Теперь, чтобы изменить пользователя как в sudoили su, мы можем сделать это только с помощью подоболочек, в противном случае мы можем сделать это только один раз:

#! /bin/zsh -

id -u # run as root

(
  USERNAME=rag
  # that's a subshell running as "rag"

  id # see all relevant group memberships are applied
)
# now back to the parent shell process running as root

(
  USERNAME=stephane
  # another subshell this time running as "stephane"

  id
)
Стефан Шазелас
источник
8

Ядро предлагает man 2 setuidи друзья.

Теперь, это работает над процессом вызова. Что еще более важно, вы не можете повысить свои привилегии. Вот почему suи sudoустановите бит SETUID, чтобы они всегда работали с самыми высокими привилегиями ( root) и соответственно переходили к нужному пользователю.

Эта комбинация означает, что вы не можете изменить UID оболочки, запустив какую-либо другую программу для этого (поэтому вы не можете ожидать sudo, что это сделает любая другая программа), и вы не можете (как оболочка) сделать это самостоятельно, если вы не готовы работать от имени пользователя root или от того же пользователя, на которого вы хотите переключиться, что не имеет смысла. Более того, как только вы откажетесь от привилегий, пути назад нет.

Мирослав Кошкар
источник
6

Ну, вы всегда можете сделать:

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

Стефан Шазелас
источник
Можете ли вы объяснить, что делает ваш сценарий?
Тряпка
@rag, ты просил об этом ...
Стефан Шазелас
1
Это ваша запись для ioshcc? Вы должны были ввести только одну строку.
ot--
-2

Команда "sudo -u ram sh" может быть использована для перехода к пользователю "ram" путем выполнения команды "sh" или shell. Команда «exit» вернет вас к первоначальному пользователю.

user45185
источник