sudo 없이 다른 사용자로 명령 목록을 실행할 수 없는 이유는 무엇입니까?

sudo 없이 다른 사용자로 명령 목록을 실행할 수 없는 이유는 무엇입니까?

이 질문은 다른 포럼에서 다양한 방식으로 질문되었습니다. 그러나 bash에서 다음을 수행할 수 없는 이유에 대한 적절한 설명이 없습니다.

#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3

일반적으로 권장되는 방법은

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

그런데 bash 스크립트 실행 중 특정 시점에 bash다른 사용자로 변경 하고 나머지 명령을 다른 사용자로 실행할 수 없는 이유는 무엇입니까?

답변1

정보가 이미 나와 있어요내 다른 답변, 하지만 거기에 묻혀 있습니다. 그래서 여기에 추가해야겠다고 생각했습니다.

bash사용자 변경에 대한 규정은 없지만 zsh있습니다.

zsh다음 변수에 값을 할당하여 사용자를 변경할 수 있습니다 .

  • $EUID:유효 사용자 ID를 변경합니다(그 이상은 아님). 일반적으로 실제 사용자 ID와 저장된 세트 사용자 ID(setuid 실행 파일에서 호출된 경우) 사이에서 또는 euid가 0인 경우 다른 ID로 euid를 변경할 수 있습니다.
  • $UID: 유효 사용자 ID, 실제 사용자 ID, 저장된 사용자 ID를 새로운 값으로 변경합니다. 새 값이 0이 아닌 이상 3개의 값이 모두 같은 값으로 설정되면 다른 값으로 변경할 수 없기 때문에 다시 돌아오지 않습니다.
  • $EGID$GID: 동일하지만 그룹 ID에 대한 것입니다.
  • $USERNAME. sudo또는 을 사용하는 것과 같습니다 su. euid, ruid, ssuid를 해당 사용자의 uid로 설정합니다. 또한 사용자 데이터베이스에 정의된 그룹 멤버십을 기반으로 egid, rgid, ssgid 및 보충 그룹을 설정합니다. for 와 마찬가지로 로 $UID설정하지 않으면 아무것도 반환되지 않지만 for 와 마찬가지로 하위 쉘의 사용자만 변경할 수 있습니다.$USERNAMEroot$UID

이 스크립트를 "루트"로 실행하는 경우:

#! /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
)

답변2

코어 man 2 setuid판매중입니다.

이제 호출 프로세스에서 작동합니다. 더 중요한 건 너야할 수 없다귀하의 특권을 높이십시오. 그렇기 때문에 항상 가장 높은 권한( )으로 실행되고 그에 따라 필요한 사용자에게 드롭다운되도록 SETUID 비트가 su설정 됩니다.sudoroot

이 조합은 다른 프로그램을 실행하여 셸 UID를 변경할 수 없으며(이것이 다른 sudo프로그램에서 이를 기대할 수 없거나 다른 프로그램에서 수행할 수 없는 이유입니다), 사용자가 직접 (셸로) 변경할 수 없음을 의미합니다. 루트로 실행하거나 사용자와 함께 실행할 의향이 있습니다. 사용자로 실행하도록 전환하고 싶었지만 이는 의미가 없습니다. 그리고 일단 특권을 포기하면 되돌릴 수 없습니다.

답변3

음, 언제든지 이렇게 할 수 있습니다:

#! /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 실행 파일을 실행하여) 저장된 세트 사용자 ID를 삭제하지 않는 한 원래 사용자 ID인 0과 다른 ID 사이에서 유효 사용자 ID를 앞뒤로 전환할 수 있습니다. ( sudo비슷하거나 일반적으로 수행되는 작업 처럼 su).

예를 들어:

$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env

이제 env유효한 사용자 ID와 저장된 설정 사용자 ID 0(내진짜사용자 ID는 여전히 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

perlsetuid/ seteuid(해당 변수 $>및 변수) 래퍼 가 있습니다 $<.

zsh도 마찬가지입니다.

$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2

위 명령은 실제 ​​사용자 ID와 세이브 세트 사용자 ID 0으로 호출되지만(세이브 세트 사용자 ID 대신 idmy를 사용하면 세이브 세트 사용자 ID만 되고 실제 사용자 ID는 여전히 1000이 됩니다) 그러나 신뢰할 수 없는 명령인 경우 여전히 약간의 피해를 입힐 수 있으므로 다음과 같이 작성해야 합니다../envsudo

$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'

(즉, 이러한 명령을 실행하기 위해 모든 uid(유효, 실제 및 저장된 세트)를 설정하십시오.

bash사용자 ID를 변경하는 방법은 없습니다. 따라서 스크립트를 호출하는 setuid 실행 파일이 있더라도 bash이는 도움이 되지 않습니다.

를 사용하면 bashuid를 변경할 때마다 setuid 실행 파일을 실행해야 합니다.

위 스크립트의 아이디어는 SWITCH_TO_USER를 호출하여 새 bash 인스턴스를 실행하여 나머지 스크립트를 실행하는 것입니다.

SWITCH_TO_USER someusersudo다른 사용자로 스크립트를 다시 실행 하지만(사용하여) 까지 스크립트 시작을 건너뛰는 함수입니다 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 0C에서 사용되는 것과 유사하며 일부 코드를 완전히 주석 처리하는 방법입니다.

:true를 반환하는 무작동(no-op)입니다. 따라서 에서는 : || :두 번째 것은 :실행되지 않습니다. 그러나 구문 분석됩니다. 이는 ( 인용된 대로) 확장이나 해석이 이루어지지 않은 << 'xxx'문서 형식입니다 .xxx

우리는 이것을 할 수 있었습니다:

: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

그러나 이는 여기에 있는 문서를 작성하여 에 표준 입력으로 전달해야 함을 의미 합니다 :.:||:

이제 까다로워지는 부분은 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 코드로 평가하고 스크립트 자체를 가져옵니다.bash_xenvsudobash$_x

_x이전에는 다음과 같이 정의되었습니다.

_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'

모든 declare, alias, shopt -p set +o출력은 쉘 내부 상태의 덤프를 구성합니다. 즉, 모든 변수, 함수, 별칭 및 옵션의 정의를 평가할 수 있는 셸 코드에 덤프합니다. 그 위에 $1배열 값(아래 참조)을 기반으로 위치 매개변수( , ...) 설정을 추가하고 스크립트의 나머지 부분에서 큰 변수가 환경에 남아 있지 않도록 일부 정리 작업을 수행합니다. 시간.$2$_a$_x

첫 번째 부분은 stderr이 ( ) set +x로 리디렉션되는 명령 그룹 내에 포함되어 있음을 알 수 있습니다 . 이는 스크립트 (또는 ) 의 특정 지점에서 실행되는 경우 해당 서문이 추적을 생성하는 것을 원하지 않기 때문에 가능한 한 방해를 최소화하기를 원하기 때문입니다. 따라서 (미리 덤프 옵션(include)이 설정되어 있는지 확인한 후 ) 실행하여 추적을 /dev/null로 보냅니다./dev/null{...} 2> /dev/nullset -xset -o xtraceset +xxtrace

stderr도 비슷한 이유로 /dev/null로 리디렉션되지만 eval "$_X"특수 읽기 전용 변수에 쓰려고 할 때 오류를 방지하기 위해서이기도 합니다.

계속해서 스크립트를 실행해 보겠습니다.

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줄로 점프하여 계속합니다.

즉, SWITCH_TO_USER줄 시작 부분에 인수 사이에 공백 하나만 두고 stat와 똑같이 작성해야 합니다.

대부분의 상태, 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이는 as alice및 as alice에 대한 권한이 있는 경우에만 작동 합니다 .sudobobbobroot

따라서 실제로는 그다지 유용하지 않습니다. su대신 (또는 sudo호출자 대신 대상 사용자를 인증 sudo하는 구성 )을 사용하는 것이 더 합리적일 수 있지만 sudo이는 여전히 모든 사용자의 비밀번호를 알아야 함을 의미합니다.

답변4

"sh" 또는 쉘 명령을 실행하여 "sudo -u ram sh" 명령을 사용하여 "ram" 사용자로 변경할 수 있습니다. "종료" 명령을 사용하면 원래 사용자로 돌아갑니다.

관련 정보