Tại sao chúng ta không thể thực thi một danh sách các lệnh như người dùng khác mà không có sudo?


8

Câu hỏi này đã được hỏi theo một cách khác trong các diễn đàn khác. Nhưng đã không có một lời giải thích hợp lý tại sao bạn không thể làm điều dưới đây trong bash.

#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3

Thông thường, cách được đề xuất là

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

nhưng tại sao không thể bashthay đổi thành một người dùng khác tại một thời điểm nào đó trong quá trình thực thi tập lệnh bash và thực thi phần còn lại của các lệnh như một người dùng khác?


Câu hỏi này cũng đã được hỏi trên Superuser, tại superuser.com/questions/93385/ , và câu trả lời này superuser.com/a/573882/76251 cung cấp phương pháp giống như TẠI ĐÂY để thay đổi người dùng trong tập lệnh.
Tim Kennedy

Câu trả lời:


4

Thông tin đã có trong câu trả lời khác của tôi , nhưng nó hơi bị chôn vùi ở đó. Vì vậy, tôi nghĩ, tôi sẽ thêm nó ở đây.

bashkhông có điều khoản để thay đổi người dùng, nhưng zshkhông.

Trong zsh, bạn thay đổi người dùng bằng cách gán giá trị cho các biến đó:

  • $EUID: thay đổi id người dùng hiệu quả (và không có gì khác). Thông thường, bạn có thể thay đổi euid của mình giữa userid thực và id người dùng đã lưu (nếu được gọi từ tệp thực thi setuid) hoặc thay đổi thành bất cứ điều gì nếu euid của bạn bằng 0.
  • $UID: thay đổi id người dùng hiệu quả, id người dùng thực và id người dùng đã lưu thành giá trị mới. Trừ khi giá trị mới là 0, sẽ không quay trở lại, vì một khi cả 3 đã được đặt thành cùng một giá trị, không có cách nào để thay đổi nó thành bất kỳ thứ gì khác.
  • $EGID$GID: điều tương tự nhưng đối với id nhóm.
  • $USERNAME. Điều đó giống như sử dụng sudohoặc su. Nó đặt euid, ruid, ssuid của bạn cho uid của người dùng đó. Nó cũng thiết lập các nhóm egid, rgid và ssgid và bổ sung dựa trên tư cách thành viên nhóm như được định nghĩa trong cơ sở dữ liệu người dùng. Giống như $UID, trừ khi bạn đặt $USERNAMEthành root, sẽ không quay trở lại, nhưng giống như $UID, bạn chỉ có thể thay đổi người dùng cho một mạng con.

Nếu bạn chạy các tập lệnh này dưới dạng "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

Bây giờ, để thay đổi người dùng như trong sudohoặc su, chúng tôi chỉ có thể thực hiện bằng cách sử dụng các lớp con, nếu không, chúng tôi chỉ có thể làm điều đó một lần:

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

Kernel cung cấp man 2 setuidvà bạn bè.

Bây giờ, nó hoạt động trên quá trình gọi. Quan trọng hơn, bạn không thể nâng cao đặc quyền của mình. Đó là lý do tại sao susudocó bit SETUID được đặt để chúng luôn chạy với các đặc quyền cao nhất ( root) và thả cho người dùng mong muốn theo đó.

Sự kết hợp đó có nghĩa là bạn không thể thay đổi UID shell bằng cách chạy một số chương trình khác để làm điều đó (đó là lý do tại sao bạn không thể mong đợi sudohoặc bất kỳ chương trình nào khác làm điều đó) và bạn không thể (như shell) tự làm trừ khi bạn sẵn sàng chạy với quyền root hoặc cùng một người dùng mà bạn muốn chuyển sang không có ý nghĩa. Hơn nữa một khi bạn bỏ đặc quyền, không có cách nào quay lại.


6

Vâng, bạn luôn có thể làm:

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

(ʘ‿ʘ)

Điều đó lần đầu tiên bắt đầu như một trò đùa vì nó thực hiện những gì được yêu cầu mặc dù có thể không chính xác như mong đợi và thực tế không hữu ích. Nhưng khi nó phát triển đến một cái gì đó hoạt động ở một mức độ nào đó và liên quan đến một vài bản hack hay, đây là một lời giải thích nhỏ:

Như Miroslav đã nói , nếu chúng ta bỏ qua các khả năng kiểu Linux (dù sao cũng không thực sự giúp được gì ở đây), cách duy nhất để một quy trình không có đặc quyền thay đổi uid là thực thi một tập tin thực thi được thiết lập.

Khi bạn nhận được đặc quyền siêu người dùng (bằng cách thực thi một tập tin thực thi setuid có chủ sở hữu là root), bạn có thể chuyển đổi id người dùng hiệu quả qua lại giữa id người dùng ban đầu, 0 và bất kỳ id nào khác trừ khi bạn từ bỏ id người dùng đã lưu của bạn ( thích những thứ như sudohoặc suthường làm).

Ví dụ:

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

Bây giờ tôi đã có một envlệnh cho phép tôi chạy bất kỳ lệnh nào với id người dùng hiệu quả và đã lưu id người dùng bằng 0 ( id người dùng thực của tôi vẫn là 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

perlcó các hàm bao tới setuid/ seteuid(những cái đó $>$<các biến).

Zsh cũng vậy:

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

Mặc dù ở trên các idlệnh đó được gọi với id người dùng thực và userid được lưu là 0 (mặc dù nếu tôi đã sử dụng ./envthay vì sudođó sẽ chỉ là userid được lưu, trong khi id người dùng thực sẽ vẫn là 1000), điều đó có nghĩa là nếu chúng là các lệnh không đáng tin cậy, chúng vẫn có thể gây ra một số thiệt hại, vì vậy bạn muốn viết nó thay vào đó như:

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

(đó là thiết lập tất cả các uids (tập hiệu quả, thực và lưu) chỉ để thực hiện các lệnh đó.

bashkhông có cách nào như vậy để thay đổi id người dùng. Vì vậy, ngay cả khi bạn có một tập hợp thực thi để gọi bashtập lệnh của mình , điều đó sẽ không giúp ích gì.

Với bash, bạn còn lại với việc thực thi một setuid thực thi mỗi khi bạn muốn thay đổi uid.

Ý tưởng trong tập lệnh ở trên là một lệnh gọi tới SWITCH_TO_USER, để thực thi một thể hiện bash mới để thực thi phần còn lại của tập lệnh.

SWITCH_TO_USER someuserít nhiều là một hàm thực thi lại tập lệnh như một người dùng khác (sử dụng sudo) nhưng bỏ qua phần bắt đầu của tập lệnh cho đến khi SWITCH_TO_USER someuser.

Điều khó khăn là chúng tôi muốn giữ trạng thái của bash hiện tại sau khi bắt đầu bash mới với tư cách là một người dùng khác.

Hãy phá vỡ nó:

{ shopt -s expand_aliases;

Chúng ta sẽ cần bí danh. Một trong những thủ thuật trong kịch bản này là bỏ qua phần kịch bản cho đến khi SWITCH_TO_USER someuser, với một cái gì đó như:

:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

Hình thức đó tương tự như #if 0được sử dụng trong C, đó là một cách để hoàn toàn nhận xét một số mã.

:là một no-op trả về đúng sự thật. Vì vậy : || :, trong lần thứ hai :không bao giờ được thực hiện. Tuy nhiên, nó được phân tích cú pháp. Và đây << 'xxx'là một hình thức của tài liệu ở đây (vì xxxđược trích dẫn), không có sự mở rộng hay giải thích nào được thực hiện.

Chúng ta có thể đã làm:

: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

Nhưng điều đó có nghĩa là tài liệu ở đây sẽ phải được viết và chuyển thành stdin :. :||:tránh điều đó

Bây giờ, nơi nó bị hack là chúng tôi sử dụng thực tế là bashmở rộng bí danh từ rất sớm trong quá trình phân tích cú pháp của nó. Để skiptrở thành một bí danh cho :||: << 'SWITCH_TO_USER someuther'một phần của cấu trúc bình luận .

Hãy tiếp tục:

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";}

Đây là định nghĩa của hàm SWITCH_TO_USER . Dưới đây chúng ta sẽ thấy rằng SWITCH_TO_USER cuối cùng sẽ là một bí danh bao quanh chức năng đó.

Hàm đó thực hiện phần lớn việc thực thi lại tập lệnh. Cuối cùng, chúng ta thấy rằng nó thực hiện lại (trong cùng một quy trình vì exec) bashvới _xbiến trong môi trường của nó (chúng ta sử dụng envở đây vì sudothường vệ sinh môi trường của nó và không cho phép vượt qua các env tùy ý). Điều đó bashđánh giá nội dung của $_xbiến đó là mã bash và nguồn chính tập lệnh.

_x được định nghĩa trước đó là:

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

Tất cả các declare, alias, shopt -p set +osản lượng tạo nên một bãi chứa của tình trạng nội bộ của vỏ. Đó là, họ kết xuất định nghĩa của tất cả các biến, hàm, bí danh và các tùy chọn dưới dạng mã shell sẵn sàng để được đánh giá. Trên hết, chúng tôi thêm cài đặt của các tham số vị trí ( $1, $2...) dựa trên giá trị của $_amảng (xem bên dưới) và một số làm sạch để $_xbiến lớn không tồn tại trong môi trường cho phần còn lại của kịch bản.

Bạn sẽ nhận thấy rằng phần đầu tiên set +xđược gói trong một nhóm lệnh có stderr được chuyển hướng đến /dev/null( {...} 2> /dev/null). Đó là bởi vì, tại một thời điểm nào đó trong tập lệnh set -x(hoặc set -o xtrace) được chạy, chúng tôi không muốn phần mở đầu đó tạo ra dấu vết như chúng tôi muốn làm cho nó ít xâm phạm nhất có thể. Vì vậy, chúng tôi chạy một set +x(sau khi đã chắc chắn để loại bỏ các xtracecài đặt tùy chọn (bao gồm ) trước) trong đó các dấu vết được gửi đến / dev / null.

Các eval "$_X"stderr cũng được chuyển hướng đến / dev / null vì những lý do tương tự mà còn để tránh những sai sót về cách viết cố gắng biến chỉ đọc đặc biệt.

Hãy tiếp tục với kịch bản:

alias skip=":||:<<'SWITCH_TO_USER $_u'"

Đó là mẹo của chúng tôi được mô tả ở trên. Trên lời gọi kịch bản ban đầu, nó sẽ bị hủy (xem bên dưới).

alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"

Bây giờ trình bao bọc bí danh xung quanh SWITCH_TO_USER. Lý do chính là để có thể truyền các tham số vị trí ( $1, $2...) cho cái mới bashsẽ diễn giải phần còn lại của tập lệnh. Chúng ta không thể làm điều đó trong SWITCH_TO_USER hàm bởi vì bên trong hàm, "$@"là các đối số cho các hàm, không phải các đối số của các tập lệnh. Chuyển hướng stderr thành / dev / null một lần nữa để ẩn xtraces và evallà để khắc phục lỗi trong bash. Sau đó, chúng tôi gọi SWITCH_TO_USER chức năng .

${_u+:} alias skip=:

Phần đó hủy bỏ skipbí danh (thay thế nó bằng lệnh :no-op) trừ khi $_ubiến được đặt.

skip

Đó là skipbí danh của chúng tôi . Trong lần gọi đầu tiên, nó sẽ chỉ là :(no-op). Trong các lần gọi lại sau đó, nó sẽ giống như : :||: << 'SWITCH_TO_USER root'.

echo test
a=foo
set a b

SWITCH_TO_USER root

Vì vậy, ở đây, như một ví dụ, tại thời điểm đó, chúng tôi khôi phục tập lệnh với tư cách là rootngười dùng và tập lệnh sẽ khôi phục trạng thái đã lưu và bỏ qua SWITCH_TO_USER rootdòng đó và tiếp tục.

Điều đó có nghĩa là nó phải được viết chính xác như stat, SWITCH_TO_USERở đầu dòng và với chính xác một khoảng trắng giữa các đối số.

Hầu hết trạng thái, stdin, stdout và stderr sẽ được giữ nguyên, nhưng không phải là các mô tả tệp khác vì sudothường đóng chúng trừ khi được cấu hình rõ ràng là không. Ví dụ:

exec 3> some-file
SWITCH_TO_USER bob
echo test >&3

thường sẽ không hoạt động.

Cũng lưu ý rằng nếu bạn làm:

SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root

Điều đó chỉ hoạt động nếu bạn có quyền sudonhư alicealicecó quyền sudonhư bob, và bobnhư root.

Vì vậy, trong thực tế, điều đó không thực sự hữu ích. Sử dụng suthay vì sudo(hoặc một sudocấu hình sudoxác thực người dùng đích thay vì người gọi) có thể có ý nghĩa hơn một chút, nhưng điều đó vẫn có nghĩa là bạn cần biết mật khẩu của tất cả những người đó.


bạn có thể giải thích kịch bản của bạn đang làm gì không?
giẻ rách

@rag, bạn đã yêu cầu nó ...
Stéphane Chazelas

1
Đó có phải là mục nhập của bạn cho ioshcc? Bạn nên nhập chỉ trong một dòng tho.
ott--

-2

Lệnh "sudo -u ram sh" có thể được sử dụng để thay đổi thành người dùng "ram", bằng cách thực hiện lệnh "sh" hoặc shell. Lệnh "exit" sẽ đưa bạn trở lại người dùng ban đầu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.