Sử dụng bit setuid đúng cách


45

Tôi có một quy trình cần quyền root khi được chạy bởi một người dùng bình thường. Rõ ràng tôi có thể sử dụng "bit setuid" để thực hiện điều này. Cách thích hợp để làm điều này trên hệ thống POSIX là gì?

Ngoài ra, làm thế nào tôi có thể làm điều này với một tập lệnh sử dụng trình thông dịch (bash, perl, python, php, v.v.)?

Câu trả lời:


53

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 ( passwdcần truy cập /etc/shadowmà 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 ./testuidnhư 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ự chownchmodđượ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_scriptsvà 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ụ.


1
nhiều shell sẽ hoạt động khác nhau nếu được gọi là -privilegedshell 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àn suid roottheo 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 ...
mikeerv

@mikeerv Tôi không quen thuộc với các khái niệm " -privilegedshell" và "script có trách nhiệm". Bạn có muốn làm một câu trả lời khác về vỏ? Tôi không thể hứa với bạn về việc đánh dấu khóa học;) Không có nhiều lý do chính đáng để làm điều này - sudolà một lựa chọn tốt hơn nhiều nếu có thể - Tôi đã viết nó như một câu trả lời chung xuất phát từ một câu hỏi ban đầu về xác thực mật khẩu hệ thống trong php .
goldilocks

1
Tôi thực sự không muốn làm điều đó - các kịch bảntrách nhiệm thực sự khó khăn - có lẽ ngoài tôi. Nhưng tôi phải nói rằng tôi không đồng ý với phân tích của bạn về sudo- tôi coi sudokẻ tâm thần ở chỗ nó giả vờ không như vậy root. Bạn thậm chí có thể đặt vỏ quả cầu trong của bạn sudoers. sutheo ý kiến ​​của tôi là tốt hơn cho các mục đích kịch bản, nhưng sudotốt cho các ứng dụng trực tiếp. Nhưng không rõ ràng như một kịch bản với một #!/bin/ksh -pbangline hoặc bất cứ điều gì suid kshtrong đó $PATHlà.
mikeerv

Trong khi câu trả lời này giải quyết được câu hỏi, các tập lệnh setuid là một công thức để tự mở ra một lỗ hổng leo thang đặc quyền địa phương. Có một lý do tại sao các kịch bản không thể được thực hiện một cách dễ dàng. Câu trả lời đúng ở đây là sudo.
mdadm

Tôi đã tự do chỉnh sửa câu trả lời của bạn để ít nhất đề cập đến lỗ hổng lớn với các biến môi trường. Tôi không thích điều này nhiều như một câu trả lời kinh điển bởi vì nó đi sâu vào rất nhiều lạc đề về một phương pháp không an toàn (trình bao bọc setuid cho các tập lệnh). Tốt nhất nên giới thiệu sudo và để lại cuộc thảo luận mở rộng cho một chủ đề khác (nhân tiện tôi đã đề cập đến nó ở đó ).
Gilles 'SO- ngừng trở nên xấu xa'

11

Có bạn có thể, nhưng nó có thể là một ý tưởng rất xấu . Thông thường, bạn sẽ không đặt bit SUID trực tiếp trên tệp thực thi của mình, nhưng sử dụng sudo (8) hoặc su (1) để thực thi nó (và giới hạn ai có thể thực thi nó)

Tuy nhiên, xin lưu ý rằng có nhiều vấn đề bảo mật khi cho phép người dùng thường xuyên chạy các chương trình (và đặc biệt là các tập lệnh!) Với quyền root. Hầu hết trong số họ phải làm điều đó trừ khi chương trình được viết cụ thể và rất cẩn thận để xử lý việc đó, người dùng độc hại có thể sử dụng nó để lấy shell root một cách dễ dàng.

Vì vậy, ngay cả khi bạn đã làm điều đó, trước tiên bạn nên vệ sinh TẤT CẢ đầu vào cho chương trình bạn muốn chạy bằng root, bao gồm môi trường, tham số dòng lệnh, STDIN và mọi tệp mà nó xử lý, dữ liệu đến từ các kết nối mạng nó mở ra, v.v ... Nó cực kỳ khó làm, và thậm chí còn khó hơn để làm đúng.

Ví dụ: bạn có thể cho phép người dùng chỉ chỉnh sửa một số tệp bằng trình chỉnh sửa. Nhưng trình soạn thảo cho phép thực thi lệnh của người trợ giúp bên ngoài hoặc tạm ngưng, do đó cung cấp cho người dùng shell root để làm như họ muốn. Hoặc bạn có thể đang thực thi các tập lệnh shell trông rất bảo mật đối với bạn, nhưng người dùng có thể chạy nó với $ IFS đã sửa đổi hoặc các biến môi trường khác thay đổi hoàn toàn hành vi của nó. Hoặc nó có thể là tập lệnh PHP được cho là thực thi "ls", nhưng người dùng chạy nó với $ PATH được sửa đổi và do đó làm cho nó chạy một shell mà anh ta đặt tên là "ls". Hoặc hàng tá vấn đề bảo mật khác.

Nếu chương trình thực sự phải thực hiện một phần công việc của nó là root, có lẽ tốt nhất nên sửa đổi nó để nó chạy như người dùng bình thường, nhưng chỉ cần thực thi (sau khi vệ sinh càng nhiều càng tốt) phần hoàn toàn cần root thông qua chương trình trợ giúp tuyệt vời.

Lưu ý rằng ngay cả khi bạn hoàn toàn tin tưởng tất cả người dùng cục bộ của mình (như, bạn cung cấp cho họ mật khẩu gốc), thì đó là một ý tưởng tồi, vì bất kỳ sự giám sát bảo mật không chủ ý nào của BẤT CỨ họ (như, có tập lệnh PHP trực tuyến hoặc mật khẩu yếu, hoặc bất cứ điều gì) đột nhiên cho phép kẻ tấn công từ xa thỏa hiệp hoàn toàn với toàn bộ máy.


1
biệt phái xem ví dụ về cách gọi an toàn trong loạt chương trình POSIX cho man sh- và thậm chí điều đó không thực hiện được command -p env -i .... Rốt cuộc là khá khó để làm. Tuy nhiên, nếu nó được thực hiện một cách chính xác, với mục đích rõ ràng, thì suidthông dịch viên có khả năng sẽ tốt hơn là sử dụng suhoặc sudo- vì hành vi của một trong hai sẽ luôn nằm ngoài tầm kiểm soát của tập lệnh (mặc dù suít có khả năng ném bóng cong hơn khi nó có xu hướng để bớt điên rồ hơn một chút) . Gói toàn bộ gói là đặt cược chắc chắn duy nhất - tốt hơn hết là chắc chắn là tất cả.
mikeerv
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.