文件访问权限:更改用户ID
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了文件访问权限:更改用户ID相关的知识,希望对你有一定的参考价值。
本文来探讨一下通过更改用户ID来获取合适的文件访问权限。由于更改组ID的规则与用户ID相同,我们在这里只探讨用户ID。
纸上得来终觉浅
先了解以下几个基本知识:
- 用户ID包括:实际用户ID、有效用户ID、保存的设置用户ID。其中保存的设置用户ID由exec函数保存。
- 实际用户ID标识我们究竟是谁,该字段在登录时取自口令文件中的登录项。通常,在一个登录会话期间该值不会改变,但root用户进程有方法改变它。
- 有效用户ID决定了我们的文件访问权限。
- 保存的设置用户ID在执行一个程序时包含了有效用户ID的副本。
- 通常,有效用户ID等于实际用户ID。
- 当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID。但是可以在文件模式字(st_mode)中设置一个特殊标志,其含义是“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)”。这个特殊标志被称为设置用户ID(set-user-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 #####
从上面的输出中可以看出,通过改变用户ID可以控制程序的访问权限。
另外注意
setuid函数与seteuid函数的区别:
相同之处:对于一个非特权用户来说,两者都可以将进程的有效用户ID设置为其实际用户ID或其保存的设置用户ID。
不同之处:对于一个特权用户来说,setuid(uid)函数将实际用户ID、有效用户ID以及保存的设置用户ID设为uid;而seteuid(uid)函数只将进程的有效用户ID设为uid。
还是那句话:因为运行设置用户ID程序的进程通常会得到额外的权限,所以编写这种程序时要特别谨慎。
以上是关于文件访问权限:更改用户ID的主要内容,如果未能解决你的问题,请参考以下文章