Các bit setuid thể được đặt trên một tập tin thực thi để khi chạy, chương trình sẽ có những đặc quyền của chủ sở hữu của các tập tin thay vì người sử dụng thực tế, nếu họ là khác nhau. Đây là sự khác biệt giữa uid hiệu quả (id người dùng) và uid thực .
Một số tiện ích phổ biến, chẳng hạn như passwd
, được sở hữu root và được cấu hình theo cách không cần thiết ( passwd
cần truy cập /etc/shadow
mà chỉ có thể được đọc bởi root).
Chiến lược tốt nhất khi làm điều này là làm bất cứ điều gì bạn cần làm như siêu người dùng ngay sau đó các đặc quyền thấp hơn để các lỗi hoặc sử dụng sai ít có khả năng xảy ra trong khi chạy root. Để làm điều này, bạn đặt uid hiệu quả của quá trình thành uid thực của nó. Trong POSIX C:
#define _POSIX_C_SOURCE 200112L // Needed with glibc (e.g., linux).
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void report (uid_t real) {
printf (
"Real UID: %d Effective UID: %d\n",
real,
geteuid()
);
}
int main (void) {
uid_t real = getuid();
report(real);
seteuid(real);
report(real);
return 0;
}
Các chức năng có liên quan, có chức năng tương đương trong hầu hết các ngôn ngữ cấp cao hơn nếu chúng được sử dụng phổ biến trên các hệ thống POSIX:
getuid()
: Lấy uid thật .
geteuid()
: Nhận uid hiệu quả .
seteuid()
: Đặt uid hiệu quả .
Bạn không thể làm bất cứ điều gì với cái cuối cùng không phù hợp với uid thực sự, ngoại trừ bit setuid được đặt trên tệp thực thi . Vì vậy, để thử điều này, biên dịch gcc test.c -o testuid
. Sau đó, bạn cần, với các đặc quyền:
chown root testuid
chmod u+s testuid
Cái cuối cùng thiết lập bit setuid. Nếu bây giờ bạn chạy ./testuid
như một người dùng bình thường, bạn sẽ thấy quy trình theo mặc định chạy với hiệu quả uid 0, root.
Thế còn kịch bản?
Điều này thay đổi từ nền tảng sang nền tảng , nhưng trên Linux, những thứ yêu cầu trình thông dịch, bao gồm mã byte, không thể sử dụng bit setuid trừ khi nó được đặt trên trình thông dịch (sẽ rất rất ngu ngốc). Đây là một tập lệnh perl đơn giản bắt chước mã C ở trên:
#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);
print "Real UID: $< Effective UID: $>\n";
$> = $<; # Not an ASCII art greedy face, but genuine perl...
print "Real UID: $< Effective UID: $>\n";
Đúng như rễ * nixy, perl đã xây dựng các biến đặc biệt cho uid ( $>
) và uid ( $<
) thực sự . Nhưng nếu bạn thử tương tự chown
và chmod
được sử dụng với tệp thực thi được biên dịch (từ C, ví dụ trước), nó sẽ không tạo ra bất kỳ sự khác biệt nào. Kịch bản không thể có được đặc quyền.
Câu trả lời cho điều này là sử dụng nhị phân setuid để thực thi tập lệnh:
#include <stdio.h>
#include <unistd.h>
int main (int argc, char *argv[]) {
if (argc < 2) {
puts("Path to perl script required.");
return 1;
}
const char *perl = "perl";
argv[0] = (char*)perl;
return execv("/usr/bin/perl", argv);
}
Biên dịch này gcc --std=c99 whatever.c -o perlsuid
, sau đó chown root perlsuid && chmod u+s perlsuid
. Bây giờ bạn có thể thực thi bất kỳ tập lệnh perl nào với uid hiệu quả bằng 0, bất kể ai sở hữu nó.
Một chiến lược tương tự sẽ hoạt động với php, python, v.v ... Nhưng ...
# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face
XIN VUI LÒNG KHÔNG để loại điều này nằm xung quanh . Rất có thể, bạn thực sự muốn biên dịch tên tập lệnh thành một đường dẫn tuyệt đối , nghĩa là thay thế tất cả mã main()
bằng:
const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
return execv("/usr/bin/perl", (char * const*)args);
Họ đảm bảo /opt/suid_scripts
và mọi thứ trong đó chỉ đọc cho người dùng không root. Nếu không, ai đó có thể trao đổi trong bất cứ điều gì cho whatever.pl
.
Ngoài ra, hãy cẩn thận rằng nhiều ngôn ngữ kịch bản cho phép các biến môi trường thay đổi cách chúng thực thi tập lệnh . Ví dụ, một biến môi trường có thể khiến một thư viện được cung cấp bởi người gọi được tải, cho phép người gọi thực thi mã tùy ý dưới dạng root. Do đó, trừ khi bạn biết rằng cả trình thông dịch và tập lệnh đều mạnh mẽ đối với tất cả các biến môi trường có thể, ĐỪNG LÀM ĐIỀU NÀY .
Vậy tôi nên làm gì thay thế?
Một cách an toàn hơn để cho phép người dùng không phải root chạy tập lệnh dưới quyền root là thêm quy tắc sudo và cho người dùng chạy sudo /path/to/script
. Sudo loại bỏ hầu hết các biến môi trường và cũng cho phép người quản trị lựa chọn chính xác ai có thể chạy lệnh và với đối số nào. Xem Làm thế nào để chạy một chương trình cụ thể với quyền root mà không cần nhắc mật khẩu? Ví dụ.
-privileged
shell và, nếu được gọi từ một script có trách nhiệm, cũng có thể được gọi một cách an toànsuid root
theo cách đó. Mặc dù vậy, tôi e rằng khái niệm về một kịch bản có trách nhiệm là một phần nhỏ trong thế giới thực. Dù sao, có lẽ đáng nói đến tầm quan trọng của việc tránh viết tất cả các loại nếu có thể trong khi quyền được nâng lên ...