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ư sudo
hoặc su
thường làm).
Ví dụ:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Bây giờ tôi đã có một env
lệ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
perl
có các hàm bao tới setuid
/ seteuid
(những cái đó $>
và $<
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 id
lệ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 ./env
thay 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 đó.
bash
khô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 bash
tậ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à bash
mở rộng bí danh từ rất sớm trong quá trình phân tích cú pháp của nó. Để skip
trở 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
) bash
với _x
biến trong môi trường của nó (chúng ta sử dụng env
ở đây vì sudo
thườ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 $_x
biế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 +o
sả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 $_a
mảng (xem bên dưới) và một số làm sạch để $_x
biế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 xtrace
cà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 bash
sẽ 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à eval
là để 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ỏ skip
bí danh (thay thế nó bằng lệnh :
no-op) trừ khi $_u
biến được đặt.
skip
Đó là skip
bí 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à root
ngườ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 root
dò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ì sudo
thườ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 sudo
như alice
và alice
có quyền sudo
như bob
, và bob
như root
.
Vì vậy, trong thực tế, điều đó không thực sự hữu ích. Sử dụng su
thay vì sudo
(hoặc một sudo
cấu hình sudo
xá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 đó.