1. 程式人生 > >文件訪問權限:更改用戶ID

文件訪問權限:更改用戶ID

erro port turn ack root 規則 log rst 編寫

本文來探討一下通過更改用戶ID來獲取合適的文件訪問權限。由於更改組ID的規則與用戶ID相同,我們在這裏只探討用戶ID。

紙上得來終覺淺

先了解以下幾個基本知識:

  1. 用戶ID包括:實際用戶ID、有效用戶ID、保存的設置用戶ID。其中保存的設置用戶ID由exec函數保存。
  2. 實際用戶ID標識我們究竟是誰,該字段在登錄時取自口令文件中的登錄項。通常,在一個登錄會話期間該值不會改變,但root用戶進程有方法改變它。
  3. 有效用戶ID決定了我們的文件訪問權限
  4. 保存的設置用戶ID在執行一個程序時包含了有效用戶ID的副本。
  5. 通常,有效用戶ID等於實際用戶ID。
  6. 當執行一個程序文件時,進程的有效用戶ID通常就是實際用戶ID。但是可以在文件模式字(st_mode
    )中設置一個特殊標誌,其含義是“當執行此文件時,將進程的有效用戶ID設置為文件所有者的用戶ID(st_uid)”。這個特殊標誌被稱為設置用戶ID(set-user-ID)位

更改這3個用戶ID的不同方法總結如下:

更改3個用戶ID的不同方法
ID exec setuid(uid)
設置用戶ID位關閉 設置用戶ID位打開 超級用戶 非特權用戶
實際用戶ID 不變 不變 設為uid 不變
有效用戶ID 不變 設置為程序文件的用戶ID 設為uid 設為uid
保存的設置用戶ID 從有效用戶ID復制 從有效用戶ID復制 設為uid 不變

關於以上幾點,我們參考一下《Unix高級環境編程》一書中給出的一個例子:

Unix系統程序/usr/bin/passwd是一個設置用戶ID程序,該文件的所有者是root用戶,而且設置了該文件的設置用戶ID位。那麽當這個程序文件由一個進程執行時,該進程具有root用戶權限。不管執行此文件的進程的實際用戶ID是什麽,都會是這樣,因為該進程的權限不是由實際用戶ID決定的,而是由有效用戶ID決定的。系統程序/usr/bin/passwd應該能將用戶的新口令寫入口令文件(一般是/etc/passwd /etc/shadow)中,而只有root用戶才具有對該文件的寫權限,所以需要使用設置用戶ID功能。因為運行設置用戶ID程序的進程通常會得到額外的權限,所以編寫這種程序時要特別謹慎。

絕知此事要躬行

假設一臺Linux主機上有2個用戶:Jim(實際用戶ID為1000)和Lucy(實際用戶ID為1001)。

Jim在自己的主目錄下有一個文件test.txt,只有自己才能讀寫。該文件的訪問權限如下所示:

[email protected]:~$ ls -l test.txt
-rw------- 1 Jim Jim 13 Sep 13 17:07 /home/Jim/test.txt

Lucy想查看該文件,卻沒有相應的權限,因此會被拒絕:

[email protected]:~$ cat /home/Jim/test.txt
cat: /home/Jim/test.txt: Permission denied

[email protected]:~$ sudo cat /home/Jim/test.txt
[sudo] password for Lucy:
hello world!

結果Lucy使用root權限強行查看了Jim的test.txt文件。這就沒什麽好說的了。不過,我們還是建議,在Linux上能不用root權限就不用root權限,用多了可能會傷及無辜。

我們自己的數據文件要由我們自己來保護。Jim可以編寫一個設置用戶ID程序,Lucy通過執行這個程序,就可以查看Jim的test.txt文件。代碼如下:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 #define BUF_LEN (256)
  5 
  6 int main(void)
  7 {
  8     FILE * fp = NULL;
  9     char buf[BUF_LEN] = {0};
 10 
 11     uid_t real_uid, effect_uid, saved_uid;
 12 
 13     if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) {
 14         perror("getresuid");
 15         return -1;
 16     }
 17 
 18     printf("===== before setuid =====\n");
 19 
 20     printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid);
 21 
 22     /*
 23      * the first important thing this program does is reduce its privilege by using setuid function.
 24      * otherwise we can not protect our data file.
 25      */
 26 
 27     // we reduce this program‘s privilege to real_uid.
 28     if (setuid(real_uid) == -1) {
 29         perror("setuid");
 30         return -1;
 31     }
 32 
 33     printf("====== after setuid with real_uid =====\n");
 34 
 35     if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) {
 36         perror("getresuid");
 37         return -1;
 38     }
 39 
 40     printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid);
 41 
 42     fp = fopen("/home/Jim/test.txt", "r");
 43     if (fp == NULL) {
 44         printf("\n##### error #####\n");
 45         perror("fopen");
 46         printf("##### error #####\n\n");
 47     }
 48 
 49     // now we raise this program‘s privilege to saved_uid.
 50     if (setuid(saved_uid) == -1) {
 51         perror("setuid");
 52         return -1;
 53     }
 54 
 55     printf("====== after setuid with saved_uid =====\n");
 56 
 57     if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) {
 58         perror("getresuid");
 59         return -1;
 60     }
 61 
 62     printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid);
 63 
 64     fp = fopen("/home/Jim/test.txt", "r");
 65     if (fp == NULL) {
 66         printf("\n##### error #####\n");
 67         perror("fopen");
 68         printf("##### error #####\n\n");
 69 
 70         return -1;
 71     }
 72 
 73     printf("\n----- file read -----\n");
 74     while (fgets(buf, BUF_LEN, fp))
 75         printf("%s", buf);
 76 
 77     if (feof(fp))
 78         printf("\nfile reach end!\n");
 79     else
 80         ferror(fp);
 81     printf("----- file close -----\n\n");
 82 
 83     fclose(fp);
 84 
 85     // reduce this program‘s privilege by setting effect_uid to saved_uid
 86     if (seteuid(saved_uid) == -1) {
 87         perror("seteuid");
 88         return -1;
 89     }
 90 
 91     printf("====== after seteuid with saved_uid =====\n");
 92 
 93     if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) {
 94         perror("getresuid");
 95         return -1;
 96     }
 97 
 98     printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid);
 99 
100     fp = fopen("/home/Jim/test.txt", "r");
101     if (fp == NULL) {
102         printf("\n##### error #####\n");
103         perror("fopen");
104         printf("##### error #####\n\n");
105     } else {
106         printf("file opened!\n\n");
107         fclose(fp);
108     }
109 
110     // reduce this program‘s privilege by setting effect_uid to real_uid
111     if (seteuid(real_uid) == -1) {
112         perror("seteuid");
113         return -1;
114     }
115 
116     printf("====== after seteuid with real_uid =====\n");
117 
118     if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) {
119         perror("getresuid");
120         return -1;
121     }
122 
123     printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid);
124 
125     fp = fopen("/home/Jim/test.txt", "r");
126     if (fp == NULL) {
127         printf("\n##### error #####\n");
128         perror("fopen");
129         printf("##### error #####\n\n");
130     } else {
131         printf("file opened!\n\n");
132         fclose(fp);
133     }
134 
135     return 0;
136 }

編譯該程序的時候要加上_GNU_SOURCE標誌(用於getresuid函數),編譯完成之後還要打開該程序的設置用戶ID標誌位。

[email protected]:~$ gcc uid_test.c -o uid_test -D _GNU_SOURCE
[email protected]:~$ chmod u+s uid_test

[email protected]:~$ ls -l uid_test
-rwsrwxr-x 1 Jim Jim 7726 Sep 14 17:19 /home/Jim/uid_test

從上面的命令輸出中可以看到,uid_test程序的設置用戶ID標誌位已經打開。Lucy可以使用這個程序來查看Jim的test.txt文件。

[email protected]:~$ /home/Jim/uid_test
===== before setuid =====
real_uid = 1001, effect_uid = 1000, saved_uid = 1000

====== after setuid with real_uid =====
real_uid = 1001, effect_uid = 1001, saved_uid = 1000


##### error #####
fopen: Permission denied
##### error #####

====== after setuid with saved_uid =====
real_uid = 1001, effect_uid = 1000, saved_uid = 1000


----- file read -----
hello world!

file reach end!
----- file close -----

====== after seteuid with saved_uid =====
real_uid = 1001, effect_uid = 1000, saved_uid = 1000

file opened!

====== after seteuid with real_uid =====
real_uid = 1001, effect_uid = 1001, saved_uid = 1000


##### error #####
fopen: Permission denied
##### error #####

[email protected]:~$

從上面的輸出中可以看出,通過改變用戶ID可以控制程序的訪問權限。

另外註意

setuid函數與seteuid函數的區別:

相同之處:對於一個非特權用戶來說,兩者都可以將進程的有效用戶ID設置為其實際用戶ID或其保存的設置用戶ID。

不同之處:對於一個特權用戶來說,setuid(uid)函數將實際用戶ID、有效用戶ID以及保存的設置用戶ID設為uid;而seteuid(uid)函數只將進程的有效用戶ID設為uid

還是那句話:因為運行設置用戶ID程序的進程通常會得到額外的權限,所以編寫這種程序時要特別謹慎。

文件訪問權限:更改用戶ID