Cách an toàn nhất để viết chương trình vào một tệp có quyền root là gì?


35

Một ứng dụng khổng lồ cần, tại một thời điểm cụ thể, để thực hiện một số lượng nhỏ ghi vào một tệp yêu cầu quyền root. Nó không thực sự là một tệp mà là một giao diện phần cứng được hiển thị với Linux dưới dạng một tệp.

Để tránh đưa ra các đặc quyền gốc cho toàn bộ ứng dụng, tôi đã viết một tập lệnh bash thực hiện các tác vụ quan trọng. Ví dụ: tập lệnh sau sẽ cho phép cổng 17 của giao diện phần cứng làm đầu ra:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

Tuy nhiên, như suidbị vô hiệu hóa cho các tập lệnh bash trên hệ thống của tôi, tôi tự hỏi đâu là cách tốt nhất để đạt được điều này.

  1. Sử dụng một số cách giải quyết được trình bày ở đây

  2. Gọi tập lệnh với sudotừ ứng dụng chính và chỉnh sửa danh sách sudoers cho phù hợp, để tránh yêu cầu mật khẩu khi gọi tập lệnh. Tôi hơi khó chịu khi trao đặc quyền cho sudo echo.

  3. Chỉ cần viết một chương trình C, với fprintfvà đặt nó thành root gốc. Hardcode chuỗi và tên tệp và đảm bảo chỉ root mới có thể chỉnh sửa nó. Hoặc đọc các chuỗi từ một tệp văn bản, tương tự đảm bảo rằng không ai có thể chỉnh sửa tệp.

  4. Một số giải pháp khác không xảy ra với tôi và an toàn hơn hay đơn giản hơn những giải pháp được trình bày ở trên?


10
Tại sao bạn không bắt đầu chương trình của mình với quyền root, mở tệp và bỏ đặc quyền? Đó là cách mọi máy chủ web hoặc như vậy làm cho ổ cắm. Thực tế, bạn không chạy như root, cũng không phải là người trợ giúp cần thiết.
Damon

Câu trả lời:


33

Bạn không cần phải cung cấp sudoquyền truy cập vào echo. Trong thực tế, điều đó là vô nghĩa bởi vì, ví dụ với sudo echo foo > bar, việc chuyển hướng được thực hiện như người dùng ban đầu, không phải là root.

Gọi tập lệnh nhỏ với sudo, cho phép NOPASSWD:truy cập CHỈ tập lệnh đó (và bất kỳ tập lệnh tương tự nào khác) bởi người dùng cần truy cập vào tập lệnh đó.

Đây luôn là cách tốt nhất / an toàn nhất để sử dụng sudo. Cô lập số lượng nhỏ các lệnh cần đặc quyền gốc vào (các) tập lệnh riêng của chúng và cho phép người dùng không tin cậy hoặc tin cậy một phần chỉ chạy tập lệnh đó dưới dạng root.

(Các) sudotập lệnh có thể nhỏ không nên lấy args (hoặc đầu vào) từ người dùng (tức là bất kỳ chương trình nào khác mà nó gọi phải có các tùy chọn và đối số được mã hóa cứng) hoặc nó phải rất cẩn thận xác thực bất kỳ đối số / đầu vào nào mà nó phải chấp nhận từ người dùng.

Hãy hoang tưởng trong việc xác nhận - thay vì tìm kiếm những điều 'đã biết xấu' để loại trừ, chỉ cho phép những điều 'đã biết tốt' và hủy bỏ bất kỳ sự không phù hợp hoặc lỗi hoặc bất cứ điều gì thậm chí đáng ngờ từ xa.

Việc xác nhận nên diễn ra càng sớm trong tập lệnh càng tốt (tốt nhất là trước khi nó làm bất cứ điều gì khác với quyền root).


Tôi thực sự nên đề cập đến điều này khi lần đầu tiên tôi viết câu trả lời này, nhưng nếu tập lệnh của bạn là tập lệnh shell thì nó PHẢI trích dẫn chính xác tất cả các biến. Đặc biệt cẩn thận để trích dẫn các biến chứa đầu vào do người dùng cung cấp theo bất kỳ cách nào, nhưng đừng cho rằng một số biến là an toàn, HÃY NHANH TAY TẤT CẢ .

Điều đó bao gồm các biến môi trường có khả năng kiểm soát bởi người sử dụng (ví dụ như "$PATH", "$HOME", "$USER"vv Và chắc chắn bao gồm "$QUERY_STRING""HTTP_USER_AGENT"vv trong một kịch bản CGI). Trong thực tế, chỉ cần trích dẫn tất cả. Nếu bạn phải xây dựng một dòng lệnh có nhiều đối số, hãy sử dụng một mảng để xây dựng danh sách args và trích dẫn rằng - "${myarray[@]}".

Tôi đã nói "trích dẫn tất cả" thường đủ chưa? nhớ nó làm đi.


18
Bạn đã quên đề cập rằng chính tập lệnh phải được sở hữu bởi root, với quyền 500.
Wildcard

13
Ít nhất là loại bỏ các quyền ghi, vì lợi ích tốt. Đó thực sự là quan điểm của tôi. Phần còn lại chỉ là cứng.
tự đại diện

10
Một chương trình C được viết cẩn thận sẽ có bề mặt tấn công nhỏ hơn so với tập lệnh shell.
dùng253751

7
@immibis, có thể. Nhưng nó sẽ mất nhiều thời gian hơn để viết và gỡ lỗi so với kịch bản shell. Và yêu cầu phải có trình biên dịch C (bị cấm trên một số máy chủ sản xuất để giảm rủi ro bảo mật bằng cách khiến kẻ tấn công khó biên dịch khai thác hơn). Ngoài ra, IMO một tập lệnh shell được viết bởi người mới đến sysadmin cấp trung cấp hoặc lập trình viên ít có khả năng khai thác hơn chương trình C được viết bởi một người có kỹ năng tương tự - đặc biệt là nếu nó phải chấp nhận và xác thực dữ liệu do người dùng cung cấp.
cas

3
@JonasWielicki - Tôi nghĩ rằng chúng ta bây giờ tốt và thực sự đi vào cõi ý kiến ​​hơn là thực tế. Bạn có thể tạo các đối số hợp lệ cho shell hoặc C (hoặc perl hoặc python hoặc awk, v.v.) hoặc ít nhiều dễ bị lỗi khai thác. Khi, thực sự, nó phụ thuộc chủ yếu vào kỹ năng của người lập trình và sự chú ý đến chi tiết (và sự mệt mỏi, vội vàng, thận trọng, v.v.). Tuy nhiên, có một thực tế là các ngôn ngữ cấp thấp hơn có xu hướng yêu cầu viết nhiều mã hơn để đạt được những gì có thể được thực hiện trong số ít dòng mã hơn trong các ngôn ngữ cấp cao hơn .... và mỗi LoC là một cơ hội khác cho một sai lầm được thực hiện.
cas

16

Kiểm tra chủ sở hữu trên các tệp gpio:

ls -l /sys/class/gpio/

Rất có thể, bạn sẽ phát hiện ra rằng họ thuộc sở hữu của nhóm gpio:

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

Trong trường hợp đó, bạn chỉ cần thêm người dùng của mình vào gpionhóm để cấp quyền truy cập mà không cần sudo:

sudo usermod -aG gpio myusername

Bạn sẽ cần phải đăng xuất và đăng nhập lại sau đó để thay đổi có hiệu lực.


Nó không hoạt động. Thật vậy, chủ sở hữu nhóm của tất cả mọi thứ trong /sys/class/gpio/là gpio, nhưng ngay cả sau khi thêm bản thân vào nhóm đó, tôi vẫn nhận được "quyền từ chối" bất cứ khi nào tôi cố gắng viết bất cứ điều gì ở đó.
vsz

1
Vấn đề là, các tệp trong /sys/class/gpio/thực tế chỉ là các liên kết tượng trưng đến /sys/devices/platform/soc/<some temporary name>/gpionơi cả chủ sở hữu và nhóm đều là root.
vsz

4
@vsz Bạn đã thử chgrp gpio /sys/devices/platform/soc/*/gpiochưa? Có lẽ một cái gì đó như thế có thể được đặt trong một tập tin khởi động.
jpa

Vâng, nhưng nó không đơn giản. Vì chúng luôn được tạo theo một cách khác, tôi đã phải sử dụng một cái gì đó nhưchgrp gpio `readlink -f /sys/class/gpio/gpio18`/*
vsz

7

Một giải pháp cho vấn đề này (được sử dụng đặc biệt trên máy tính để bàn Linux nhưng cũng có thể áp dụng trong các trường hợp khác) là sử dụng D-Bus để kích hoạt một dịch vụ nhỏ chạy dưới quyền root và polkit để thực hiện ủy quyền. Đây là cơ bản những gì polkit được thiết kế cho; từ tài liệu giới thiệu của nó :

polkit cung cấp API ủy quyền dự định sẽ được sử dụng bởi các chương trình đặc quyền (Dịch vụ cung cấp dịch vụ của MECHANISMS)) cho các chương trình không có đặc quyền (CÂU HỎI THƯỜNG XUYÊN). Xem trang hướng dẫn polkit cho kiến ​​trúc hệ thống và hình ảnh lớn.

Thay vì thực hiện chương trình trợ giúp của bạn, chương trình lớn, không có đặc quyền sẽ gửi yêu cầu trên xe buýt. Người trợ giúp của bạn có thể đang chạy như một daemon bắt đầu khi khởi động hệ thống, hoặc, tốt hơn, được kích hoạt khi cần bởi systemd . Sau đó, người trợ giúp đó sẽ sử dụng polkit để xác minh rằng yêu cầu đến từ nơi được ủy quyền. (Hoặc, trong trường hợp này, nếu cảm thấy quá mức cần thiết, bạn có thể sử dụng một số cơ chế xác thực / ủy quyền được mã hóa cứng khác.)

Tôi đã tìm thấy một bài viết cơ bản tốt về giao tiếp qua D-Bus và trong khi tôi chưa kiểm tra nó, đây dường như là một ví dụ cơ bản về việc thêm polkit vào hỗn hợp .

Trong phương pháp này, không có gì cần phải được đánh dấu setuid.


5

Một cách để làm điều này là tạo một chương trình gốc setuid được viết bằng C chỉ thực hiện những gì cần thiết và không có gì hơn. Trong trường hợp của bạn, nó không phải xem xét bất kỳ đầu vào của người dùng nào cả.

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}

Không có cách nào để vượt qua điều này thông qua các biến môi trường hoặc bất cứ điều gì, bởi vì tất cả những gì nó làm là thực hiện một vài cuộc gọi hệ thống.

Nhược điểm: bạn nên kiểm tra giá trị trả về của mỗi cuộc gọi hệ thống.

Ưu điểm: kiểm tra lỗi thực sự dễ dàng: nếu có bất kỳ lỗi nào, chỉ cần perrorvà bảo lãnh: thoát với trạng thái khác không. Nếu có lỗi, hãy điều tra với strace. Bạn không cần chương trình này để cung cấp các thông báo lỗi thực sự tốt đẹp.


2
Tôi có thể bị cám dỗ để mở cả hai tập tin trước khi viết bất cứ điều gì để bảo vệ chống lại sự vắng mặt của lần thứ hai. Và tôi có thể chạy chương trình này thông qua sudođể nó không cần phải được thiết lập. Ngẫu nhiên, nó là <fcntl.h>dành cho open().
Jonathan Leffler

3

Bless tee thay vì echo cho sudo là một cách phổ biến để tiếp cận một tình huống mà bạn cần hạn chế các perm gốc. Chuyển hướng đến / dev / null là để ngăn chặn bất kỳ đầu ra nào bị rò rỉ - tee thực hiện những gì bạn muốn.

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null

5
Nếu bạn cho phép teechạy cùng sudo, bạn đang cho phép BẤT K file tệp nào được ghi đè hoặc gắn vào (ví dụ: bao gồm /etc/passwd- chỉ cần thêm một tài khoản uid = 0 mới). Bạn cũng có thể cho phép tất cả các lệnh được chạy với sudo(BTW /etc/sudoersthuộc tập hợp 'BẤT K files tệp nào để có thể ghi đè lên sudo tee)
cas

2
Rõ ràng, bạn có thể giới hạn các tệp teecó thể ghi vào, như được mô tả trong câu trả lời này trên một câu hỏi riêng biệt. Chỉ cần đảm bảo rằng bạn cũng đọc các bình luận, vì một số người có vấn đề với cú pháp ban đầu được sử dụng và đề nghị sửa lỗi cho vấn đề đó.
Alex

1
@alex, vâng, bạn có thể làm điều đó với bất kỳ lệnh nào trong sudo - hạn chế các đối số cho phép. Cấu hình có thể rất dài và phức tạp nếu bạn muốn cho phép teehoặc bất cứ điều gì để hoạt động trên nhiều tệp với sudo.
cas

0

Bạn có thể tạo một kịch bản thực hiện nhiệm vụ mong muốn của bạn. Sau đó, tạo một tài khoản người dùng mới chỉ có thể đăng nhập bằng cách cung cấp khóa được sử dụng bởi OpenSSH.

Lưu ý: Bất kỳ ai cũng có thể chạy tập lệnh đó nếu họ có tệp khóa, vì vậy hãy đảm bảo rằng tệp khóa OpenSSH không thể đọc được bởi bất kỳ ai mà bạn muốn ngăn chặn thực hiện tác vụ.

Trong cấu hình OpenSSH (trong tệp ủy quyền), trước khi chỉ định khóa, chỉ định một lệnh (theo sau là khoảng trắng), như được mô tả trong văn bản "ví dụ" này:

command="myscript" keydata optionalComment

Tùy chọn cấu hình này có thể hạn chế khóa OpenSSH chỉ chạy một lệnh cụ thể. Bây giờ bạn đã cấp quyền, nhưng cấu hình OpenSSH là một phần của giải pháp thực sự được sử dụng để hạn chế / giới hạn những gì người dùng có thể làm, để người dùng không chạy các lệnh khác. Điều này cũng không phức tạp với tệp cấu hình "sudo", vì vậy nếu bạn sử dụng OpenBSD hoặc nếu "doas" mới ("làm như") của OpenBSD bắt đầu trở nên phổ biến hơn (với các phiên bản tương lai của bất kỳ hệ điều hành nào bạn sử dụng) , bạn sẽ không cần phải bị thách thức bởi sự phức tạp trong cấu hình sudo.

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.