為何UNIX Linux中會有suid程式

2021-05-21 19:35:57 字數 4748 閱讀 9071

linux的單點驗證我已經說了不止一次了,linux的整體設計是機制和策略相分離的,單點驗證顯然是策略方面的東西,因此驗證本身並沒有核心的介入,那麼什麼是驗證本身呢?其實就是諸如最簡單的的密碼驗證和稍微複雜一點的指紋,聲音或者瞳孔驗證,不管怎麼說這些都是策略,核心不應該介入,因此核心當中你無法知道怎麼儲存和驗證使用者的密碼是否正確,這些都是使用者空間完成的,這個事實似乎會讓linux的初學者很驚訝,像安全驗證這麼重要的事情怎麼會沒有核心介入呢?這是因為linux的機制和策略分離的特性造成的。linux靠uid,euid以及suid機制來實現這一切,最後可以證明這三者是缺一不可的,三者非常緊湊。以下的分析不考慮組的概念也不怎麼考慮selinux的新策略。

uid是可執行檔案的所有者的uid或者是可執行檔案呼叫者的uid,它是乙個靜態的概念,而euid是有效uid,是乙個動態的概念,事實上執行緒的行為判斷是通過euid來進行的,uid只是乙個參考,正如程序優先順序的概念,動態優先順序是計算所得的,最終通過動態優先順序來判決,而靜態優先順序僅僅是乙個參考。euid就是程序實際有效的uid,作為判決的標準生效;而suid的意義比較特殊,可能可執行檔案的uid,euid都是非root,但是該可執行檔案需要執行一些特殊的操作而必須擁有root許可權,那麼該可執行檔案會臨時擁有root許可權,這樣的可執行檔案擁有suid屬性,這種suid屬性是必須的,否則普通使用者就無法su成root使用者了,因為普通使用者程序的uid和euid都為非0,按照傳統的能力模型,非0的uid/euid是無法改變其uid/euid的,呆會兒我再解釋為何uid和euid非0的程序為何改變其euid,先看下面的**(2.6.24核心):

asmlinkage long sys_setuid(uid_t uid)

...//見下面

既然uid非0的程序無法改變其uid,那麼euid為0就成了普通使用者成為root的最後的救命稻草,其實執行中的程序只能改變其euid,uid是其可執行檔案的內稟屬性,改變它沒有意義,因此某種意義上euid才是有意義的,如果沒有euid屬性,那麼普通使用者程序基本沒有機會公升級為root程序,普通程序的子程序還是普通程序,如此反覆將永遠沒有機會,但是suid使得普通程序可以成為root程序,/bin/su就是其中的一例,成為root程序是需要驗證的,這就是使用者空間的驗證邏輯,當普通使用者程序呼叫su -的時候,其實su的uid還是普通程序,但是因為su是具有suid屬性的,因此其euid是root,那麼接下來只要使用者空間的驗證通過,exec出來的shell將是root的,這就是單點驗證的實質,如果su程式寫的不好,比如根本不通過驗證,只是exec乙個shell,那麼很顯然這個shell是root的,但是核心不管這些,核心只認識euid,真正的驗證以及怎麼驗證留給使用者空間的邏輯,雖然su是suid的,只要它exec乙個shell那麼該shell就是root的,但是su不會實現的這麼傻,它內部實現了密碼驗證邏輯,只有通過驗證才可以exec乙個root的shell。

euid表示了乙個程序的特權級別歸屬--root和non-root,而euid為root的程序exec的新程序仍然屬於root級別,只有root所有的suid程式才會有上述功能,乙個普通使用者的程式即使設定了suid位也沒有用,其實suid的真實含義就是不從父程序繼承euid,而用該可執行檔案的所有者的id作為 euid。linux實現了傳統unix的基於使用者的簡單驗證和後來的能力模型,此二者其實是相互結合的,只有root使用者才會被賦予能力,這其實是為了防止root權力過大而導致系統漏洞而引入的,實際上是位於root上層的又一級的認證體系,首先先從setuid的實現開始闡述:

asmlinkage long sys_setuid(uid_t uid)

int old_euid = current->euid;

int old_ruid, old_suid, new_ruid, new_suid;

int retval;

retval = security_task_setuid(uid, (uid_t)-1, (uid_t)-1, lsm_setid_id);

if (retval)

return retval;

old_ruid = new_ruid = current->uid;

old_suid = current->suid;

new_suid = old_suid;

if (capable(cap_setuid)) else if ((uid != current->uid) && (uid != new_suid)) //euid非root的程序只能控制在自己的名字集內

return -eperm;

current->fsuid = current->euid = uid;

current->suid = new_suid;

...//以下的post_setuid做了收尾工作,其實就是清除掉一些能力

return security_task_post_setuid(old_ruid, old_euid, old_suid, lsm_setid_id);

static inline void cap_emulate_setxuid (int old_ruid, int old_euid, int old_suid)

if ((old_ruid == 0 || old_euid == 0 || old_suid == 0) &&

(current->uid != 0 && current->euid != 0 && current->suid != 0) //注意只要uid,euid,suid有乙個為0,就說明該程序還會回到root能力級別,因為程序可以隨時更改uid或者別的id來搖身一變成為別的級別的成員

&&!current->keep_capabilities) //以下兩行正式將euid設定給當前程序,此時當前程序已經是新的程序了,這個函式的呼叫堆疊從load_elf_binary的最後開始

current->suid = current->euid = current->fsuid = bprm->e_uid;

current->sgid = current->egid = current->fsgid = bprm->e_gid;

if (current->pid != 1) {

current->cap_permitted = new_permitted;

current->cap_effective = cap_intersect (new_permitted, bprm->cap_effective);

current->keep_capabilities = 0;

void compute_creds(struct linux_binprm *bprm)

int unsafe;

if (bprm->e_uid != current->uid)

suid_keys(current);

exec_keys(current);

task_lock(current);

unsafe = unsafe_exec(current);

task_unlock(current);

linux的能力模型是乙個很複雜的模型,有很多的能力可以設定,並且有很多的配置規則。

上面是傳統unix的root使用者兩級機制和能力模型融合的核心實現,在使用者空間必須實現驗證策略,其實就是密碼驗證,指紋驗證等策略。正是由於suid 的存在,乙個普通的使用者程序才能su出乙個root的shell或者別的使用者的shell,如果沒有suid屬性的存在,那麼任何普通使用者將無法成為 root使用者,因為核心並不進行驗證,而只有核心有權更改使用者程序的uid,那麼核心外必須有程序來**核心進行驗證,該程序必須是絕對可信任的,su是乙個suid程式,它**root執行保持最高許可權只有這樣才可以保證驗證結果的可信,它進行實際的驗證工作,通過後才會fork-exec出乙個 root使用者的shell或者別的使用者的shell,因此對su的攻擊將是致命的攻擊,一定要保護好su而不被替換掉,其實只要拿到root特權,你可以自己寫乙個suid程式,然後它的子程序將是有特權的,以下就是乙個例子:

以下是乙個suid的程式,編譯好之後用chmod 7777 test將之設定為最開放的suid程式:

#include

#include

int main()

printf("uid:%d/neuid:%d/negid:%d/n",getuid(),geteuid(),getegid());

setuid(0);

printf("new uid:%d/n",getuid());

execve("/home/zhaoya/temp",null,envp);

以下程式用zhaoya使用者在/home/zhaoya目錄下編譯為temp:

#include

#include

#include

int main()

printf("uid:%d/neuid:%d/n",getuid(),geteuid());

int fd = open("/root/install.log",o_wronly);

perror("open");

直接執行temp會得出沒有許可權錯誤,而用test啟動則成功開啟root目錄下的檔案,從輸出也可以看出上述核心的行為,直接執行temp的輸出是 500,500,而通過test啟動的輸出則為0,0.如果將test中的setuid(0);注釋掉,那麼啟動temp的結果也是成功開啟root目錄的檔案,但是輸出變成了500,0,總之geteuid的結果為0,那麼它的許可權就是root,而現代linux的root許可權直接對應完整的能力集,而 root使用者可以通過能力模型的系統呼叫介面從cap_effective去除當前不需要的能力,而cap_permitted並不改變,root使用者可以通過介面將某些能力置位,其實能力模型主要就是限制root使用者的,對於別的使用者本身他們就沒有什麼許可權就不用再限制了,對root使用者的限制可以提高安全性。

為何我會有傷悲

你走了,有人一直說這 不明智你走了,很多人一直 說你炒作 你是如此狠心,拋下我自己 我是如此恍惚,無法面對突然沒有你 我問自己,為何我會有傷悲?也許,是你的 追求太過遠大 讓我無法祈望,更談不上追趕 也許,你不應出現在這星球 因為我們,只是那 乙個個單細胞變形蟲 我知道,我沒有理由埋怨 因為,你跟我...

C語言裡為何會有「2 2 5」的結果

int main 有些童鞋可能會說,這不是偷換概念嗎,拿字串和int相加,是滴,但在這裡請這些童鞋暫且幽默一回,想一想為何a b會得出5的結果?你們實際動手編譯了嗎?結果是為5嗎?我動手編譯了,結果不是5,確切的說是乙個不可列印的ascii字元,所以console顯示的是 2 2 稍對c堆疊布局略有...

機器學習演算法推導 矩陣求導中為何會有「轉置」?

之前學習神經網路推導的時候,發現在有的求導上最後結果需要轉置,而有的不需要,很困惑 正向傳播 反向傳播 這裡為什麼要對w進行轉置操作?為什麼別處有的地方就沒有轉置操作?思來想去,突然發現自己對矩陣求導似乎一無所知,遂有 博主第一次參考的部落格 接下來就是博主的總結了 在數學上,矩陣微積分是用來表示多...