允許在 shell 腳本上設定 setuid

允許在 shell 腳本上設定 setuid

權限setuid位元告訴 Linux 使用所有者而不是執行者的有效使用者 ID 來運行程式:

> 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

然而,這只適用於可執行檔; shell 腳本忽略 setuid 位元:

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

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

1000

維基百科:

由於安全漏洞的可能性增加,許多作業系統在應用於可執行 shell 腳本時會忽略 setuid 屬性。

假設我願意接受這些風險,有沒有辦法告訴 Linux 在 shell 腳本上與在可執行檔上一樣對待 setuid 位元?

如果沒有,是否有解決此問題的通用解決方法?我目前的解決方案是添加一個sudoers條目以允許ALL以我希望其運行的用戶身份運行給定的腳本,以NOPASSWD避免密碼提示出現。這樣做的主要缺點是每次我想要執行此操作時都需要輸入一個sudoers條目,並且呼叫者需要sudo some-script而不只是some-script

答案1

Linux 忽略所有解釋的可執行檔(即以行開頭的可執行檔)上的 setuid1 位元#!。這comp.unix.questions 常見問題解答解釋了 setuid shell 腳本的安全性問題。這些問題有兩種:與shebang相關的和與shell相關的;我將在下面詳細介紹。

如果您不關心安全性並希望在 Linux 下允許 setuid 腳本,則需要修補核心。從 3.x 核心開始,我認為您需要添加對install_exec_creds在裡面load_script函數,在呼叫之前open_exec,但我還沒有測試過。


塞圖伊德謝邦

#!shebang ( ) 典型的實現方式存在固有的競爭條件:

  1. 內核打開可執行文件,發現它以#!.
  2. 核心關閉可執行檔並開啟解釋器。
  3. 核心將腳本的路徑插入到參數清單中(如argv[1]),並執行解釋器。

如果此實作允許 setuid 腳本,則攻擊者可以透過以下方式呼叫任意腳本:建立指向現有 setuid 腳本的符號鏈接,執行該腳本,並在核心執行步驟 1 之後、解釋器開始執行之前安排變更連結。其第一個論點。為此原因,大多數 unice 都會忽略 setuid 位當他們檢測到一個shebang。

確保此實現安全的一種方法是內核鎖定腳本文件,直到解釋器打開它(請注意,這不僅必須防止取消連結或覆蓋文件,而且還必須防止重命名路徑中的任何目錄)。但 Unix 系統往往迴避強制鎖,而符號連結會使正確的鎖功能變得特別困難和侵入性。我認為沒有人這樣做。

一些unix系統(主要是OpenBSD、NetBSD和Mac OS X,所有這些都需要核心設定才能啟用)實現安全性 setuid shebang使用附加功能:路徑引用文件描述符上已開啟的文件/dev/fd/N(所以打開大致相當於)。許多 UNIX 系統(包括 Linux)有但沒有 setuid 腳本。/dev/fd/Ndup(N)/dev/fd

  1. 內核打開可執行文件,發現它以#!.假設可執行檔的檔案描述符是 3。
  2. 核心打開解釋器。
  3. 核心插入/dev/fd/3參數清單(如argv[1]),並執行解釋器。

Sven Mascheck 的 shebang 頁面在unices上有很多關於shebang的信息,包括setuid 支持


Setuid 解譯器

假設您已設法使程式以 root 身份運行,因為您的作業系統支援 setuid shebang,或者因為您使用了本機二進位包裝器(例如sudo)。你打開了安全漏洞嗎?或許。這裡的問題是不是關於解釋程式與編譯程式。問題是你是否運行時系統如果以特權執行,則行為安全。

  • 任何動態連結的本機二進位可執行檔都以某種方式由動態載入器(如/lib/ld.so),載入程式所需的動態程式庫。在許多unice上,您可以透過環境(LD_LIBRARY_PATH是環境變數的通用名稱)配置動態庫的搜尋路徑,甚至可以將其他程式庫載入到所有執行的二進位檔案(LD_PRELOAD)。程式的呼叫者可以透過放置特製的程式碼(以及其他策略)在該程式的上下文libc.so中執行任意程式碼。$LD_LIBRARY_PATH所有健全的系統都會忽略LD_*setuid 可執行檔中的變數。

  • 貝殼例如 sh、csh 及其衍生物,環境變數自動成為 shell 參數。透過PATHIFS等參數,腳本的呼叫者有很多機會在 shell 腳本的上下文中執行任意程式碼。如果某些 shell 偵測到已使用特權呼叫腳本,則它們會將這些變數設為合理的預設值,但我不知道是否存在我信任的任何特定實作。

  • 大多數運行時環境(無論是本機、字節碼或解釋型)都具有類似的功能。很少有人在 setuid 可執行檔中採取特殊的預防措施,儘管運行本機程式碼的可執行檔通常不會做任何比動態連結(確實採取預防措施)更奇特的事情。

  • 珀爾是一個值得注意的例外。它明確支援 setuid 腳本以安全的方式。事實上,即使您的作業系統忽略了腳本上的 setuid 位,您的腳本也可以執行 setuid。這是因為 perl 附帶了一個 setuid root 幫助程序,它執行必要的檢查並以所需的權限重新呼叫所需腳本的解釋器。這在佩爾塞克手動的。過去需要 setuid perl 腳本來#!/usr/bin/suidperl -wT代替#!/usr/bin/perl -wT,但在大多數現代系統上,#!/usr/bin/perl -wT就足夠了。

請注意,使用本機二進位文件包裝器本身沒有做任何事情來防止這些問題。事實上,它可以使情況更差,因為它可能會阻止您的運行時環境檢測到它是使用特權呼叫的並繞過其運行時可配置性。

本機二進位包裝器可以讓 shell 腳本安全,如果包裝器淨化環境。腳本必須注意不要做出太多假設(例如關於當前目錄),但這就是事實。您可以使用 sudo 來執行此操作,前提是它已設定為清理環境。將變數列入黑名單很容易出錯,因此始終將其列入白名單。使用 sudo,請確保env_reset選項已開啟、已setenv關閉,並且env_fileenv_keep包含無害變數。


TL、博士:

  • Setuid shebang 不安全,但通常被忽略。
  • 如果您使用特權執行程式(透過 sudo 或 setuid),請編寫本機程式碼或 perl,或使用清理環境的包裝器啟動程式(例如帶有選項的 sudo env_reset)。

1如果您將“setgid”替換為“setuid”,則此討論同樣適用;Linux 核心在腳本上都會忽略它們

答案2

解決此問題的一種方法是從可以使用 setuid 位元的程式中呼叫 shell 腳本。
它類似於 sudo。例如,以下是在 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;
 }

將其儲存為 setuid-test2.c。
現在
,在該程式二進位檔案上執行 setuid:

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

現在,您應該能夠運行它,並且您將看到您的腳本在沒有任何人權限的情況下執行。
但在這裡,您也需要對腳本路徑進行硬編碼,或將其作為命令列參數傳遞給上面的 exe。

答案3

我在這艘船上添加了一些腳本前綴:

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

請注意,這不會使用,setuid而只是執行當前文件sudo

答案4

如果由於某種原因sudo不可用,您可以用 C 編寫一個瘦包裝腳本:

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

編譯後將其設定setuidchmod 4511 wrapper_script.

這與另一個發布的答案類似,但在乾淨的環境中運行腳本並明確使用/bin/bash而不是由 調用的 shell system(),因此消除了一些潛在的安全漏洞。

請注意,這會完全丟棄環境。如果你想使用一些環境變數而不打開漏洞,你真的只需要使用sudo.

顯然,您要確保腳本本身只能由 root 寫入。

相關內容