轉載自http://www.cnblogs.com/stemon/p/5287631.html
一、基本概念
實際用戶ID(RUID):用于標識一個系統中用戶是誰,一般是在登錄之后,就被唯一的確定,就是登錄的用戶的uid。
有效用戶ID(EUID):用于系統決定用戶對系統資源的權限,也就是說當用戶做任何一個操作時,最終看它有沒有權限,都是在判斷有效用戶ID是否有權限。如果有,則ok,否則報錯不能執行。在正常的情況下,一個用戶登錄之后(假設是A用戶),A用戶的有效用戶ID和實際用戶ID是相同的,但是如果A用戶在某些場景中想要執行一些特權操作,能順利的執行嗎?上面說到了用戶的任務操作,linux內核都是通過檢驗有效用戶ID來判斷當前執行這個操作的用戶是否具有權限。這里的A用戶想要執行的是特權操作,A用戶沒有這個權限,所以A用戶就只能通過一定的手段來修改當前的有效用戶ID使其具有執行特權操作的權限。
這里總結一句話:為什么要修改進程的有效用戶ID,就是想在某一時刻能夠執行一些特權操作。
設置用戶ID位:用于對外的權限的開放,它的作用是修改進行的有效用戶ID,給進程賦予臨時的特權。
保存的設置用戶ID:是有效用戶ID副本,既然是有效用戶ID的副本,那么它的作用肯定是為了以后恢復有效用戶ID。
這里涉及很多的ID,通過下圖看一下這些ID都是屬于誰:
下面說一下文件設置用戶ID位,這個ID僅僅是一個二進制的bit位,在文件stat結構的st_mode成員中,對于一般的文件,該位是置為無效的,只有可執行文件的該位是置為有效的。
二、改變三個用戶ID的方法
下面這幅圖給出了改變實際用戶ID、有效用戶ID和保存的設置用戶ID的方法
再來一幅圖看一下文件的ID和進程的ID在權限訪問上的對應關系。對于一個普通文件,有三個ID,這三個ID對應三組權限,這三組權限控制著進程對該文件的訪問權限。
在注意一點:ID并不是一個int或者一個標識,他是一個操作系統用戶的標號,該用戶創建的所有的進程都是這個ID。
對于linux系統,某個用戶登錄后,創建一個文件,那個這個文件的用戶ID就是這個用戶的ID。該用戶創建的所有的進程都可以訪問這個文件,因為該用戶創建的進程的實際用戶ID和有效用戶ID都是這個用戶的ID。但是當一個用戶創建的進程要去訪問其他用戶創建的文件的時候,就需要用到有效用戶ID的改變,來能夠有權限訪問這個文件。
1. 進程打開、創建、刪除文件時的權限測試?
測試的參與者是進程的有效用戶id、有效組id、文件的擁有者id、文件擁有者組id。
(1)進程的有效用戶id是0(超級用戶),則允許訪問;
(2)進程的有效用戶id等于文件擁有者id,則按照擁有者的權限訪問;
(3)進程有效組id或附加組id之一等于文件擁有者組id,則按照文件擁有者組的權限訪問;
(4)否則,按照文件的其他用戶訪問權限訪問;
上述的四步是按續依次進行的。
2. 什么時候用到設置用戶id和設置組id?
當進程通過exec函數執行某個文件的時候,而且文件的設置用戶id只會影響到進程的有效用 戶id
(其實也足夠了,因為只有有效用戶id參與權限測試),這樣說不夠嚴謹,因為進程的保存設置用
戶id和保存設置組id會被exec函數從有效用戶
id和有效組id復制過來,所以保存的設置用戶id和保存的設置組id也會隨之改變。
(1)當文件的設置用戶id位和設置組id位沒有打開:
進程的有效用戶id和有效組id保持不變,嚴格按照第2步進行權限測試;
(2)文件的設置用戶id位和設置組id位被打開:
exec函數才會把進程的有效用戶id和有效組id設置為文件擁有者的用戶id和組id,這時再進
行權限測試,進程就擁有了和文件擁有者一樣的訪問權限。
3. 進程保存設置用戶id和保存設置組id有什么用?
顧名思義,這兩個id存在的價值就是保存,保存誰呢?保存有效用戶id和有效組id。當進程的
實際用戶id和有效用戶id不同時(組id同理),保存的設置用戶id才有意義。因為這樣就可以通
過調用setuid()把有效用戶id切換為與進程的實際用戶id或保存的設置用戶id相同的值,不保存下
來,我們就弄丟了。
三、具體實例分析
實例分析一:
如何在權限不夠的情況下執行特權權限,具體的方法就是更改進程的有效用戶ID。
對于linux系統來說,用戶的密碼都存放在/etc/shadow文件下,查看一下這個文件的權限:
假如我是一個普通的用戶,顯然我是可以修改我自己的密碼的,通過passwd命令,無可厚非,自己修改自己的密碼當然是允許的。
但是仔細想想有沒有什么不對的地方,作為一個普通的用戶登錄后,我的所有的進程的實際用戶ID和有效用戶ID都應該是我自己(這個用戶)的UID。從上面對/etc/shadow文件用戶ID和所屬的組ID看,我不具備修改這個文件的權限,那么執行passwd命令是怎么修改我的密碼的呢?
根據上面所講的知識,決定進程對文件的訪問權限的是執行操作時的有效用戶ID,所以在執行passwd命令時,進行的有效用戶ID肯定不是我這個普通用戶的UID,一定被修改過了。
還有一點,在執行passwd這個命令的時候,在磁盤上肯定有一個可執行的文件,下面看看這個可執行文件的權限:
我們看到了一個s,這就是使用戶設置ID位有效,上面說過這個位的作用就是修改執行這個可執行文件的進程的有效用戶ID,那么我么來看看他是怎樣修改執行passwd命令的進程的有效用戶ID的。
首先看一下命令的執行的過程,當普通用戶執行passwd命令時,shell會fork出一個子進程,此時該進程的有效用戶ID還是這個普通用戶的ID,然后exec程序執行/usr/bin/passwd這個例程(可執行文件)。通過上面的表我們就能知道,exec發現/usr/bin/passwd這個可執行文件有SUID為,于是會把進程的有效用戶ID設置成可執行文件的用戶ID,顯示是root,此時這個進程就獲得了root的權限,得到了讀寫/etc/shadow文件的權限,從而普通用戶可以完成密碼的修改。exec進程退出后,會恢復普通用戶的有效用戶ID為實際用戶ID(也就是該普通用戶的ID),這樣就保證了不會是普通用戶一直具有root權限。
在exec時,修改進程的有效用戶ID為文件的用戶ID,還是恢復進程的有效用戶ID為進程的實際用戶ID,這些都是linux內核完成的,用戶是不能干預這些步驟的。
這樣的過程既實現了普通用戶權限的暫時的提升,不讓普通用戶長久的擁有root權限。同時即使在普通用戶擁有root權限的這段短暫的時間里,普通用戶也不能為所欲為,因為普通用戶的這個進程必須按照/usr/bin/passwd這個可執行文件的指令內容執行,不能干之外的任何事情,而這個可執行文件又是root事先寫好的,多么完美的設計啊~~
這就是設置用戶ID為的作用,它的存在就是為了普通用戶能夠在某些時候獲取一段時間的超級權限,但是你可能疑問,為什么不能用setuid直接修改呢?
如果這里可以用setuid直接修改進程的有效用戶ID來獲得特權權限,那么整個系統的超級權限就不可控制了,這違背了最小權限模型。所以linux的設計是:setuid在非特權用戶下面,有效用戶ID只能設置成為實際用戶ID或者保存的設置用戶ID。保存的設置用戶ID又是有效用戶ID的副本,有效用戶ID只能是實際用戶ID或者文件的所有者ID(在設置了保存用戶ID位時)。
這樣你就不能將有效用戶ID設置成隨意的值,所以對普通用戶創建的任何文件,如果沒有得到超級用戶的授權,那么無論怎么編寫代碼來設置運行進程的有效用戶ID或者設置用戶ID位,由于這個可執行文件是普通用戶自己編寫的,所以權限根本沒有任何改變。這里就是說只有root自己創建的可執行文件并且設置了用戶ID位,才能夠提升執行該可執行文件的進程的權限。
實例分析二:
上面的例子中沒有用到保存的設置用戶ID,看看這個ID的作用。既然保存的設置用戶ID是有效用戶ID的副本,那么肯定是為了在某個時刻用于恢復原先的有效用戶ID的,這樣就能夠實現用戶權限的切換。
例如:man命令(這是AUP上面的例子,當然實際linux上好像不是這么實現的,不過還是哪這個當做例子說明)
man程序文件的所有者和其它屬組通常都是man自身保留的用戶和組,man可能需要執行許多其它命令,以處理包含顯示手冊頁的文件,為了防止被欺騙和重寫錯誤文件,man在兩種權限之間切換。運行man命令用戶的權限,擁有man可執行文件用戶的權限。下面列出其工作步驟:
(1)man擁有者是名為man的用戶,且設置用戶ID位已經設置。當我們exec此程序的時候,用戶ID的情況是:
1 2 3 | 實際用戶ID=我們的用戶ID 有效用戶ID=man //設置用戶ID位已經設置,有效用戶ID改變 保存的設置用戶ID=man |
(2)進程訪問需要的配置文件和手冊頁,這些文件由man用戶擁有,因為有效用戶ID是man,所以可以訪問。
(3)man代表我們執行任何一個命令之前,調用setuid(getuid()),我們不是超級用戶,所以僅僅改變有效用戶ID,現在用戶ID的情況變為:
1 2 3 | 實際用戶ID=我們的用戶ID(沒有改變) 有效用戶ID=我們的用戶ID 保存的設置用戶ID=man |
看出來了吧,如果不保存下來,我們就把man用戶丟了,再想setuid(man)就不會成功了,因為只允許將有效用戶ID設置為與實際用戶ID或者保存的設置用戶ID相同的值。這時候,我們做回了自己。就只能訪問我們通常可以訪問的東西,沒有特殊的權限了。man代表我們安全執行一次過濾。
(4)執行完過濾后,man再調用setuid(euid)(這里的euid是man的數值用戶id,man自己通過geteuid獲得并保存)。因為euid等于保存的設置用戶ID,設置成功。
我們這時得到:
1 2 3 | 實際用戶ID=我們的用戶ID(未改變) 有效用戶ID=man 保存的設置用戶ID=man(未改變) |
(5)因為有效用戶ID是man,所以現在可以對其他文件進行操作了。
四、新文件和新目錄的所有權
1. 新文件的用戶ID設置為進程的有效用戶ID
2. 新文件的組ID有兩種情況:
- 新文件的組ID為進程的有效組ID
- 如果新文件的組ID所在的目錄的設置組ID位設置了的話,新文件的組ID將設為目錄的組ID。
3. 其他
- mkdir函數自動的傳遞了目錄的設置組ID位。
- access函數是用設計用戶ID和實際組ID來進行訪問權限的測試的,如果你只是想測試一下實際用戶所擁有的權限,這個函數就可以了。
五、編程應用
1. 在執行exec前后,進程的實際用戶ID和實際組ID不變,而有效ID取決于所執行的程序文件是否設置了設置用戶ID位和設置組ID位, 如果設置了設置用戶ID位,則有效用戶ID變成程序文件所有者ID,否則有效用戶ID不變。對組ID處理方式與此相同
2. 保存的設置用戶ID是由exec復制有效用戶ID后得來的,所有說設置用戶ID是有效用戶ID的副本!
3. 一般設計應用程序時,總是試圖使用最小特權模型,依照模型,程序應當只具有為完成任務所需的最小特權.
4. 使用函數:
1 2 3 4 5 6 7 8 | #include <unistd.h> int ?setuid(uid_t uid);? //設置實際用戶ID和有效用戶ID,如果進程為超級用戶,則會設置實際、有效、保存的設置用戶ID int ?setgid(gid_t gid);? //設置實際組ID和有效組ID int ?setreuid(uid_t ruid, uid_t euid);? //交換實際用戶ID和有效用戶ID int ?setregid(gid_t rgid, gid_t egid); int ?seteuid(uid_t uid);? //更改有效用戶ID int ?setegid(gid_t gid); |
注意:?
根據書上來看,特權用戶使用這幾個函數的時候,都是直接用參數的值來設置實際用戶ID或者有效用戶ID,這些值都可以是任意的。
例如:setreuid(ruid, euid), 如果是特權用戶,則直接設置實際用戶ID為ruid,有效用戶ID為euid。
但非特權用戶就不行了,非特權用戶用setuid(),seteuid()則只能將有效有戶ID設置為實際用戶ID或保存的設置用戶ID,如果不是這兩個數,設置失敗!
對編程應用方面的總結:
非特權用戶不能指定任意的有效用戶ID, 結合其他幾個函數來交換或設置有效用戶ID, 與設置用戶ID位一起實現權限的控制。