Cho phép setuid trên tập lệnh shell


184

Các setuidchút phép nói với Linux để chạy một chương trình với id người dùng hiệu quả của chủ sở hữu thay vì người thi hành:

> cat setuid-test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    printf("%d", geteuid());
    return 0;
}

> gcc -o setuid-test setuid-test.c
> ./setuid-test

1000

> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test

65534

Tuy nhiên, điều này chỉ áp dụng cho thực thi; shell script bỏ qua bit setuid:

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2

1000

Wikipedia nói :

Do khả năng lỗi bảo mật tăng lên, nhiều hệ điều hành bỏ qua thuộc tính setuid khi áp dụng cho các tập lệnh shell thực thi.

Giả sử tôi sẵn sàng chấp nhận những rủi ro đó, có cách nào để bảo Linux xử lý bit setuid giống như trên các kịch bản shell như trên các tệp thực thi không?

Nếu không, có một cách giải quyết chung cho vấn đề này? Giải pháp hiện tại của tôi là thêm một sudoersmục nhập để cho phép ALLchạy một tập lệnh đã cho với tư cách là người dùng tôi muốn nó chạy, NOPASSWDđể tránh lời nhắc mật khẩu. Nhược điểm chính của điều đó là nhu cầu sudoersnhập cảnh mỗi khi tôi muốn làm điều này và nhu cầu của người gọi sudo some-scriptthay vì chỉsome-script

Câu trả lời:


202

Linux bỏ qua bit setuid¹ trên tất cả các tệp thực thi được giải thích (nghĩa là các tệp thực thi bắt đầu bằng một #!dòng). Câu hỏi thường gặp comp.unix.questions giải thích các vấn đề bảo mật với tập lệnh shell setuid. Những vấn đề này có hai loại: liên quan đến shebang và liên quan đến vỏ; Tôi đi vào chi tiết hơn dưới đây.

Nếu bạn không quan tâm đến bảo mật và muốn cho phép các tập lệnh setuid, trong Linux, bạn sẽ cần vá kernel. Kể từ hạt nhân 3.x, tôi nghĩ bạn cần thêm một cuộc gọi install_exec_credsvào load_scriptchức năng, trước khi gọi đến open_exec, nhưng tôi chưa thử nghiệm.


Setuid shebang

Có một điều kiện chủng tộc vốn có theo cách shebang ( #!) thường được thực hiện:

  1. Nhân mở tệp thực thi và thấy rằng nó bắt đầu bằng #!.
  2. Hạt nhân đóng thực thi và thay vào đó mở trình thông dịch.
  3. Hạt nhân chèn đường dẫn đến tập lệnh vào danh sách đối số (as argv[1]) và thực thi trình thông dịch.

Nếu tập lệnh setuid được cho phép với triển khai này, kẻ tấn công có thể gọi một tập lệnh tùy ý bằng cách tạo một liên kết tượng trưng đến tập lệnh setuid hiện có, thực thi nó và sắp xếp để thay đổi liên kết sau khi kernel đã thực hiện bước 1 và trước khi trình thông dịch chuyển sang mở đối số đầu tiên của nó. Vì lý do này, hầu hết các thông báo bỏ qua bit setuid khi họ phát hiện ra một shebang.

Một cách để bảo mật việc triển khai này là cho kernel khóa tệp script cho đến khi trình thông dịch đã mở nó (lưu ý rằng điều này phải ngăn chặn không chỉ hủy liên kết hoặc ghi đè tệp, mà còn đổi tên bất kỳ thư mục nào trong đường dẫn). Nhưng các hệ thống unix có xu hướng né tránh các khóa bắt buộc và các liên kết tượng trưng sẽ làm cho một tính năng khóa chính xác đặc biệt khó khăn và xâm lấn. Tôi không nghĩ có ai làm theo cách này.

Một vài hệ thống unix (chủ yếu là OpenBSD, NetBSD và Mac OS X, tất cả đều yêu cầu cài đặt kernel) để triển khai setuid shebang an toàn bằng cách sử dụng một tính năng bổ sung: đường dẫn đề cập đến tệp đã được mở trên bộ mô tả tệp N (vì vậy mở là gần tương đương với ). Nhiều hệ thống unix (bao gồm cả Linux) có nhưng không có tập lệnh setuid./dev/fd/N/dev/fd/Ndup(N)/dev/fd

  1. Nhân mở tệp thực thi và thấy rằng nó bắt đầu bằng #!. Giả sử mô tả tệp cho tệp thực thi là 3.
  2. Nhân mở trình thông dịch.
  3. Nhân chèn /dev/fd/3danh sách đối số (dưới dạng argv[1]) và thực thi trình thông dịch.

Trang shebang của Sven Mascheck có rất nhiều thông tin về shebang trên các đơn vị, bao gồm hỗ trợ setuid .


Thông dịch viên Setuid

Giả sử bạn đã quản lý để làm cho chương trình của bạn chạy bằng root, vì hệ điều hành của bạn hỗ trợ setuid shebang hoặc vì bạn đã sử dụng trình bao bọc nhị phân gốc (chẳng hạn như sudo). Bạn đã mở một lỗ hổng bảo mật? Có lẽ . Vấn đề ở đây không phải là về diễn giải so với các chương trình được biên dịch. Vấn đề là hệ thống thời gian chạy của bạn có hoạt động an toàn hay không nếu được thực thi với các đặc quyền.

  • Bất kỳ thực thi nhị phân gốc được liên kết động nào theo cách được giải thích bởi trình tải động (ví dụ /lib/ld.so), tải các thư viện động theo yêu cầu của chương trình. Trên nhiều thông báo, bạn có thể định cấu hình đường dẫn tìm kiếm cho các thư viện động thông qua môi trường ( LD_LIBRARY_PATHlà tên chung cho biến môi trường) và thậm chí tải các thư viện bổ sung vào tất cả các nhị phân đã thực hiện ( LD_PRELOAD). Các Invoker của chương trình có thể thực thi mã tùy ý trong bối cảnh của chương trình bằng cách đặt một đặc biệt crafted libc.sotrong $LD_LIBRARY_PATH(giữa chiến thuật khác). Tất cả các hệ thống lành mạnh bỏ qua các LD_*biến trong thực thi setuid.

  • Trong các shell như sh, csh và các dẫn xuất, các biến môi trường sẽ tự động trở thành các tham số shell. Thông qua các thông số như PATH, IFS, và nhiều hơn nữa, các Invoker của kịch bản có nhiều cơ hội để thực thi mã tùy ý trong bối cảnh các kịch bản shell của. Một số shell đặt các biến này thành mặc định lành mạnh nếu chúng phát hiện ra rằng tập lệnh đã được gọi với các đặc quyền, nhưng tôi không biết rằng có bất kỳ triển khai cụ thể nào mà tôi sẽ tin tưởng.

  • Hầu hết các môi trường thời gian chạy (cho dù là bản địa, mã byte hoặc giải thích) có các tính năng tương tự. Rất ít người thực hiện các biện pháp phòng ngừa đặc biệt trong các tệp thực thi setuid, mặc dù các mã chạy mã gốc thường không làm gì lạ hơn liên kết động (điều này có các biện pháp phòng ngừa).

  • Perl là một ngoại lệ đáng chú ý. Nó rõ ràng hỗ trợ các tập lệnh setuid một cách an toàn. Trong thực tế, tập lệnh của bạn có thể chạy setuid ngay cả khi hệ điều hành của bạn bỏ qua bit setuid trên tập lệnh. Điều này là do các tàu perl có trình trợ giúp gốc setuid thực hiện các kiểm tra cần thiết và khôi phục trình thông dịch trên các tập lệnh mong muốn với các đặc quyền mong muốn. Điều này được giải thích trong hướng dẫn perlsec . Nó đã từng là các tập lệnh peru setuid cần thiết #!/usr/bin/suidperl -wTthay vì #!/usr/bin/perl -wT, nhưng trên hầu hết các hệ thống hiện đại, #!/usr/bin/perl -wTlà đủ.

Lưu ý rằng việc sử dụng trình bao bọc nhị phân riêng không làm gì để ngăn chặn những vấn đề này . Trong thực tế, nó có thể làm cho tình hình tồi tệ hơn , bởi vì nó có thể ngăn môi trường thời gian chạy của bạn phát hiện ra rằng nó được gọi với các đặc quyền và bỏ qua cấu hình thời gian chạy của nó.

Trình bao bọc nhị phân riêng có thể làm cho tập lệnh shell an toàn nếu trình bao bọc vệ sinh môi trường . Kịch bản phải lưu ý không đưa ra quá nhiều giả định (ví dụ về thư mục hiện tại) nhưng điều này sẽ đi. Bạn có thể sử dụng sudo cho điều này với điều kiện là nó được thiết lập để vệ sinh môi trường. Các biến trong danh sách đen dễ bị lỗi, vì vậy luôn có danh sách trắng. Với sudo, đảm bảo rằng env_resettùy chọn được bật, setenvtắt và điều đó env_fileenv_keepchỉ chứa các biến vô hại.


TL, DR:

  • Setuid shebang không an toàn nhưng thường bị bỏ qua.
  • Nếu bạn chạy một chương trình với các đặc quyền (thông qua sudo hoặc setuid), hãy viết mã gốc hoặc perl hoặc khởi động chương trình với một trình bao bọc vệ sinh môi trường (chẳng hạn như sudo với env_resettùy chọn).

¹ Thảo luận này áp dụng như nhau nếu bạn thay thế “setgid” cho “setuid”; cả hai đều bị bỏ qua bởi nhân Linux trên các tập lệnh


2
@Josh: Các kịch bản shell setuid an toàn là có thể, nhưng chỉ khi cả trình triển khai shell và trình soạn thảo script đều rất cẩn thận. Thay vì mã gốc, tôi khuyên Perl, nơi những người triển khai đã quan tâm rằng các tập lệnh setuid nên được bảo mật với ít nỗ lực về phần của người viết kịch bản.
Gilles

3
rõ ràng các suidperlcông cụ đã bị phản đối và được đánh dấu để loại bỏ trong nhiều năm (nhưng vẫn tồn tại không phải là ít)
jmtd

7
Trên thực tế suidperlđã bị xóa kể từ perl 5.11 (ổn định 5.12): perl5110delta:> "suidperl" đã bị xóa. Nó được sử dụng để cung cấp một cơ chế mô phỏng các bit cho phép setuid trên các hệ thống không hỗ trợ nó đúng cách. perl5120delta:> "suidperl" không còn là một phần của Perl. Nó được sử dụng để cung cấp một cơ chế mô phỏng các bit cho phép setuid trên các hệ thống không hỗ trợ nó đúng cách.
Randy Stauner

5
Cũng lưu ý dòng này từ tài liệu perl 5.6.1 (gần một thập kỷ trước) ... perl561delta:> Lưu ý rằng suidperl không được xây dựng cũng như không được cài đặt theo mặc định trong bất kỳ phiên bản gần đây nào của perl. Sử dụng suidperl rất không khuyến khích . Nếu bạn nghĩ rằng bạn cần nó, hãy thử các lựa chọn thay thế như sudo trước. Xem Courtesan.com/sudo .
Randy Stauner

3
Tôi không hiểu: đây dường như là một lời giải thích về lý do của các vấn đề, nhưng có câu trả lời thực sự cho câu hỏi của OP ở đây không? Có cách nào để bảo hệ điều hành của tôi chạy tập lệnh shell setuid không?
Tom

55

Một cách để giải quyết vấn đề này là gọi tập lệnh shell từ một chương trình có thể sử dụng bit setuid.
nó giống như sudo. Ví dụ, đây là cách bạn sẽ thực hiện điều này trong chương trình C:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid( 0 );   // you can set it at run time also
    system( "/home/pubuntu/setuid-test2.sh" );
    return 0;
 }

Lưu nó dưới dạng setuid-test2.c.
biên dịch
Bây giờ thực hiện setuid trên chương trình nhị phân này:

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

Bây giờ, bạn sẽ có thể chạy nó và bạn sẽ thấy tập lệnh của mình được thực thi mà không có quyền.
Nhưng ở đây, bạn cũng cần mã hóa đường dẫn kịch bản hoặc chuyển nó dưới dạng dòng lệnh arg sang trên exe.


25
Tôi sẽ khuyên bạn không nên cho phép truyền tập lệnh dưới dạng đối số dòng lệnh, vì điều đó về cơ bản mang lại cho bất kỳ ai có thể thực thi chương trình khả năng chạy bất kỳ tập lệnh nào như người dùng đã xác định.
dsp

39
Lưu ý rằng NÀY KHÔNG ĐẢM BẢO ngay cả khi đường dẫn đầy đủ đến tập lệnh được mã hóa cứng. Shell sẽ kế thừa các biến từ môi trường và nhiều trong số chúng cho phép kẻ xâm phạm tiêm mã tùy ý. PATHLD_LIBRARY_PATHlà các vectơ rõ ràng. Một số vỏ thực hiện $ENVhoặc $BASHENVhoặc ~/.zshenvthậm chí trước khi họ bắt đầu thực hiện các kịch bản thích hợp, vì vậy bạn không thể bảo vệ khỏi những lúc tất cả từ bên trong kịch bản. Cách an toàn duy nhất để gọi một kịch bản shell với các đặc quyền là làm sạch môi trường . Sudo biết cách làm điều đó một cách an toàn. Vì vậy, đừng viết trình bao bọc của riêng bạn, hãy sử dụng sudo .
Gilles

28
Tôi cảm thấy tồi tệ khi anh ấy đột nhiên bị hạ thấp vì điều này - tôi đặc biệt nói rằng tôi cũng muốn nghe các phiên bản không an toàn, và tôi đang tưởng tượng một chương trình thực thi lấy một đối số kịch bản shell khi tôi nói. Rõ ràng là nó không an toàn ồ ạt, nhưng tôi muốn biết những khả năng nào tồn tại
Michael Mrozek

8
@Gilles: FYI, Linux hủy cài đặt LD_LIBRARY_PATHtrong số những thứ khác khi nó gặp bit setuid.
grawity

3
Thay vì sử dụng system, bạn có thể thấy đơn giản hơn (và hiệu quả hơn) khi sử dụng một trong những execgia đình - rất có thể execve. Bằng cách đó, bạn không tạo quy trình mới hoặc bắt đầu trình bao và bạn có thể chuyển tiếp các đối số (giả sử rằng tập lệnh đặc quyền của bạn có thể xử lý các đối số một cách an toàn).
Toby Speight

23

Tôi tiền tố một vài tập lệnh trong thuyền này, do đó:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Lưu ý rằng điều này không sử dụng setuidmà chỉ thực hiện các tập tin hiện tại với sudo.


4
Điều này không sử dụng setuid mà chỉ cung cấp cho bạn một sudodấu nhắc. (Đối với tôi, toàn bộ quan điểm của setuid là cho phép mọi thứ chạy như root mà không cần sudo.)
Luc

12

Nếu bạn muốn tránh gọi, sudo some_scriptbạn chỉ cần làm:

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

Các chương trình SETUID cần được thiết kế hết sức cẩn thận khi chúng chạy với quyền root và người dùng có quyền kiểm soát lớn đối với chúng. Họ cần phải tỉnh táo - kiểm tra mọi thứ. Bạn không thể làm điều đó với các tập lệnh vì:

  • Shell là những phần mềm lớn tương tác nhiều với người dùng. Gần như không thể kiểm tra tất cả mọi thứ - đặc biệt là vì hầu hết các mã không có ý định chạy trong chế độ như vậy.
  • Các kịch bản là một giải pháp chủ yếu nhanh chóng và thường không được chuẩn bị cẩn thận đến mức chúng sẽ cho phép setuid. Chúng có nhiều tính năng nguy hiểm tiềm tàng.
  • Họ phụ thuộc rất nhiều vào các chương trình khác. Nó không đủ để kiểm tra vỏ. sed, awkvv cũng cần phải được kiểm tra

Xin lưu ý rằng sudocung cấp một số kiểm tra độ tỉnh táo nhưng không đủ - kiểm tra từng dòng trong mã của riêng bạn.

Như một lưu ý cuối cùng: xem xét sử dụng khả năng. Chúng cho phép bạn cung cấp một quy trình đang chạy như một đặc quyền đặc biệt của người dùng thường yêu cầu quyền root. Tuy nhiên, ví dụ, trong khi pingcần thao tác mạng, nó không cần phải có quyền truy cập vào các tệp. Tôi không chắc chắn tuy nhiên nếu chúng được thừa kế.


2
Tôi đoán là /ust/bin/envnên /usr/bin/env.
Bob

4

Bạn có thể tạo bí danh cho sudo + tên của tập lệnh. Tất nhiên, đó là công việc thậm chí còn nhiều hơn để thiết lập, vì sau đó bạn cũng phải thiết lập bí danh, nhưng nó giúp bạn không phải gõ sudo.

Nhưng nếu bạn không quan tâm đến những rủi ro bảo mật khủng khiếp, hãy sử dụng shell setuid làm trình thông dịch cho tập lệnh shell. Không biết điều đó sẽ làm việc cho bạn, nhưng tôi đoán nó có thể.

Hãy để tôi nói rằng tôi khuyên không nên thực sự làm điều này, mặc dù. Tôi chỉ đề cập đến nó cho mục đích giáo dục ;-)


5
Nó sẽ làm việc. Thật khủng khiếp như bạn đã nêu. Bit SETUID cho phép thực thi với quyền của chủ sở hữu. Shell setuid (trừ khi nó được thiết kế để hoạt động với setuid) sẽ chạy dưới quyền root cho bất kỳ người dùng nào. Tức là bất cứ ai cũng có thể chạy rm -rf /(và các lệnh khác trong chuỗi KHÔNG LÀM NÓ TẠI NHÀ ).
Maciej Piechotka

9
@MaciejPiechotka bởi ĐỪNG LÀM NÓ TẠI NHÀ, ý bạn là Hãy thoải mái làm việc đó tại nơi làm việc? :)
peterph

4

lệnh super [-r reqpath] [args]

Super cho phép người dùng được chỉ định thực thi các tập lệnh (hoặc các lệnh khác) như thể chúng là root; hoặc nó có thể đặt các nhóm uid, gid và / hoặc bổ sung trên cơ sở mỗi lệnh trước khi thực hiện lệnh. Nó được dự định là một sự thay thế an toàn để tạo tập lệnh setuid root. Super cũng cho phép người dùng thông thường cung cấp các lệnh để thực thi bởi người khác; chúng thực thi với uid, gid và các nhóm người dùng cung cấp lệnh.

Super giới thiệu tệp `` super.tab '' để xem người dùng có được phép thực thi lệnh được yêu cầu hay không. Nếu được phép, super sẽ thực thi pgm [args], trong đó pgm là chương trình được liên kết với lệnh này. (Root được phép thực thi theo mặc định, nhưng vẫn có thể bị từ chối nếu quy tắc loại trừ root. Người dùng thông thường không được phép thực thi theo mặc định.)

Nếu lệnh là một liên kết tượng trưng (hoặc liên kết cứng) với siêu chương trình, thì việc gõ% lệnh args tương đương với việc nhập% super lệnh args (Lệnh không được siêu, hoặc siêu sẽ không nhận ra rằng nó được gọi thông qua một liên kết.)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubfox.com/manpages/utopic/en/man1/super.1.html


Xin chào và chào mừng đến với trang web! Chúng tôi hy vọng câu trả lời sẽ được chi tiết hơn ở đây. Bạn có thể chỉnh sửa câu trả lời của mình và giải thích chương trình này là gì và nó có thể giúp giải quyết vấn đề của OP như thế nào không?
terdon

Cảm ơn bạn Nizam, trong nhiều giờ cố gắng tìm thứ gì đó như siêu cho các tài khoản được phép thực thi bởi người dùng khác với tư cách là người dùng tệp.
Chad

2

Nếu vì lý do nào đó sudokhông có sẵn, bạn có thể viết một tập lệnh bao bọc mỏng trong C:

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}

Và một khi bạn biên dịch nó sẽ đặt nó như setuidvới chmod 4511 wrapper_script.

Điều này tương tự với một câu trả lời được đăng khác, nhưng chạy tập lệnh với môi trường sạch sẽ và sử dụng rõ ràng /bin/bashthay vì shell được gọi bởi system(), và do đó sẽ đóng một số lỗ hổng bảo mật tiềm năng.

Lưu ý rằng điều này loại bỏ hoàn toàn môi trường. Nếu bạn muốn sử dụng một số biến môi trường mà không mở các lỗ hổng, bạn thực sự chỉ cần sử dụng sudo.

Rõ ràng, bạn muốn chắc chắn rằng tập lệnh chỉ có thể ghi được bằng root.


-3

Lần đầu tiên tôi tìm thấy câu hỏi này, không bị thuyết phục bởi tất cả các câu trả lời, đây là một câu hỏi hay hơn nhiều, nó cũng cho phép bạn hoàn toàn làm xáo trộn kịch bản bash của bạn, nếu bạn đang rất nghiêng!

Nó nên được tự giải thích.

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

Sau đó bạn chạy

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded

Tại sao các downvote ???
Theodore R. Smith

2
Có lẽ bởi vì đó là một giải pháp quá phức tạp, thực hiện thao tác chuỗi và đã nhúng mã shell. Hoặc bạn tạo một launcher đơn giản, hoặc bạn sử dụng sudo. Đây là điều tồi tệ nhất trong tất cả các lựa chọn.
siride
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.