Scripting

正確使用 setuid 位

  • April 5, 2020

我有一個由普通使用者執行時需要 root 權限的程序。顯然我可以使用“setuid bit”來完成這個。在 POSIX 系統上執行此操作的正確方法是什麼?

另外,如何使用使用解釋器(bash、perl、python、php 等)的腳本來執行此操作?

可以在執行檔上設置setuid 位,以便在執行時,如果程序不同,程序將擁有文件所有者而不是真實使用者的權限。這就是有效uid(使用者id)和真實uid的區別。

一些常見的實用程序,例如passwd,是擁有 root 的,並且出於必要而以這種方式配置(passwd需要訪問/etc/shadow,只能由 root 讀取)。

執行此操作時的最佳策略是立即以超級使用者身份執行您需要執行的任何操作,然後降低權限,以便在執行 root 時不太可能發生錯誤或誤用。為此,您將程序的有效uid 設置為其真實 uid。在 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;
}

相關函式,如果它們在 POSIX 系統上普遍使用,它們應該在大多數高級語言中具有等價物:

  • getuid(): 獲取真實的uid。
  • geteuid(): 獲取有效的uid。
  • seteuid():設置有效的uid。

除非在執行檔上設置了 setuid 位,否則您不能對不適合真實 uid 的最後一個執行任何操作。所以要試試這個,編譯gcc test.c -o testuid. 然後,您需要具有以下權限:

chown root testuid
chmod u+s testuid

最後一個設置 setuid 位。如果您現在./testuid以普通使用者身份執行,您將看到該程序預設以有效 uid 0、root 執行。

腳本呢?

這因平台而異,但在 Linux 上,需要解釋器的東西(包括字節碼)不能使用 setuid 位,除非它在解釋器上設置(這將非常非常愚蠢)。這是一個模仿上面 C 程式碼的簡單 perl 腳本:

#!/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"; 

忠實於它的 *nixy 根源,perl 為有效的 uid ( $>) 和真實的 uid ( $<) 建構了特殊變數。但是,如果您嘗試相同的方法chownchmod與已編譯的(來自 C,上一個範例)執行檔一起使用,則不會有任何區別。腳本無法獲得權限。

答案是使用 setuid 二進製文件來執行腳本:

#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);
}

編譯這個gcc --std=c99 whatever.c -o perlsuid,然後chown root perlsuid && chmod u+s perlsuid。您現在可以執行任何有效 uid 為 0 的 perl 腳本,而不管它的所有者是誰。

類似的策略適用於 php、python 等。 但是……

# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face

不要把這種東西留在身邊 最有可能的是,您實際上希望以腳本的名稱作為絕對路徑進行編譯,即將所有程式碼替換為main()

   const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
   return execv("/usr/bin/perl", (char * const*)args);

他們確保/opt/suid_scripts其中的所有內容對於非 root 使用者都是只讀的。否則,有人可以用任何東西交換whatever.pl.

此外,請注意許多腳本語言允許環境變數改變它們執行腳本的方式。例如,環境變數可能會導致呼叫者提供的庫被載入,從而允許呼叫者以 root 身份執行任意程式碼。因此,除非您知道解釋器和腳本本身對所有可能的環境變數都具有強韌性,否則不要這樣做

那麼我應該怎麼做呢?

允許非 root 使用者以 root 身份執行腳本的更安全方法是添加sudo規則並讓使用者 run sudo /path/to/script。Sudo 去除了大多數環境變數,還允許管理員精細地選擇誰可以執行命令以及使用什麼參數。請參閱如何在沒有密碼提示的情況下以 root 身份執行特定程序?例如。

引用自:https://unix.stackexchange.com/questions/166817