实际用户ID,有效用户ID和设置用户ID(S_ISUID)

本文深入解析了UNIX系统中的权限管理机制,重点介绍了实际用户ID (RUID)、有效用户ID (EUID) 和设置用户ID (SUID) 的概念及其作用。通过具体的实例,如passwd命令如何帮助普通用户修改受保护的/etc/shadow文件中的密码,详细阐述了这些概念的实际应用。

 看UNIX相关的书时经常能遇到这几个概念,但一直没有好好去理清这几个概念,以致对这几个概念一直一知半解。今天好好区分了一下这几个概念并总结如下。说白了这几个UID引出都是为了系统的权限管理。

 

    下面分别用RUID, EUID,SUID来表示实际用户ID,有效用户ID,设置用户ID。另外用户ID是个整型数,为了说明方便真接使用了用户名来代表不同的UID。先解释一下这几个ID的作用:

RUID, 用于在系统中标识一个用户是谁,当用户使用用户名和密码成功登录后一个UNIX系统后就唯一确定了他的RUID.

EUID, 用于系统决定用户对系统资源的访问权限,通常情况下等于RUID。

SUID,用于对外权限的开放。跟RUID及EUID是用一个用户绑定不同,它是跟文件而不是跟用户绑定。

 

    说明SUID的时候很多书都简略的提了一下passwd这个程序,下面就拿这个例子来分析。我们知道linux系统的密码都存在了/etc/shadow这个文件里。这个文件是如此的重要,在做任何修改之前最好先备份一下。查看/etc/shadow文件的属性如下:

 

[root@localhost ~]# ll /etc/shadow

-r-------- 1 root root 1144 Jul 20 22:33 /etc/shadow

 

从上可以看出/etc/shadow文件是一个属于root用户及root组的文件,并且只有EUID为root的用户具有读的权限,其它所有EUID都没有任何权限。当你在steve用户(EUID此时也为steve)的shell下试图用vim打开这个文件时会提示权限不允许。至于连root用户也只有读的权限我猜是为了不鼓励root用户使用vim类的编辑器去直接修改它,而要采用passwd命令来修改这个文件。如果你非要直接修改它,那么你可以使用chmod命令修改为属性为root可写,然后就可以修改了。

 

    用过UNIX系统的人都知道,任何一个用户都可以使用passwd这个命令来得新设定自己的密码。但从上面已经知道,非root用记是无法读这个文件的,那么普通用户是如何做到修改这个文件的呢?我们知道passwd这个命令实际执行的程序是/usr/bin/passwd, 查看这个文件属性如下:

 

-r-s--x--x 1 root root 21944 Feb 12  2006 /usr/bin/passwd;

 

对应文件存取标志的s位就是通常说的SUID位,另外可以看到所有用户都有执行的这个程序权力。当steve用户执行passwd命令的时候。Shell会fork出一个子进程,此时进程的EUID还是steve,然后exec程序/usr/bin/passwd。exec会根据/usr/bin/passwd的SUID位会把进程的EUID设成root,   此时这个进程都获得了root权限, 得到了读写/etc/shadow文件的权限, 从而steve用户可完成密码的修改。 exec退出后会恢复steve用户的EUID为steve.这样就不会使steve用户一直拥有root权限。

 

我们可以测试一下,用root用户把/usr/bin/passwd的SUID位去掉,如下:

[root@localhost ~]# ll /usr/bin/passwd 

-r-s--x--x 1 root root 21944 Feb 12  2006 /usr/bin/passwd

[root@localhost ~]# chmod u-s /usr/bin/passwd

[root@localhost ~]# ll /usr/bin/passwd       

-r-x--x--x 1 root root 21944 Feb 12  2006 /usr/bin/passwd

 

然后steve用户用命令passwd去更新密码会提示如下错误:

[steve@localhost ~]$ passwd

Changing password for user steve.

Changing password for steve

(current) UNIX password:

passwd: Authentication token manipulation error

[steve@localhost ~]$

这就是因为/usr/bin/passwd程序的SUID去掉后,steve用户虽然可以执行该程序,但因为/usr/bin/passwd/的SUID没有设置,这样exec后进程的EUID仍为steve的原因。

 

    也许有人会发现root用户却仍可以使用该用命修改密码,那是因为root用户本身的EUID时就是root (也有可能只要发现是RUID是root就不检查EUID了,直接可读写,root就是老大嘛), 可以读取密码文件。

 

另外也许有人会发现普通的文件文件普通的文本文件会也可以设置SUID位, 但这是没有意义的,因为文件文件没有地方执行seteuid()的系统调用来改变当用用户的EUID。

 

最后,这里的对用户ID的规则同样也适用了组ID。

帮我阅读httpd代码 void accept_request(void *arg) { //pthread_create传过来的参数(void *)(intptr_t)client_sock int client = (intptr_t)arg; char buf[1024]; /*size_t是标准C库中定义的,应为unsigned int,在64位系统中为 long unsigned int。数据类型"socklen_t"int应该具有相同的长度, 否则就会破坏 BSD套接字层的填充。*/ size_t numchars; char method[255]; char url[255]; char path[512]; size_t i, j; /*在使用这个结构体方法时,需要引入:<sys/types.h>、<sys/stat.h> struct stat这个结构体是用来描述一个linux系统文件系统中的文件属性 的结构。有两种方法获得一个文件的属性 第一种是通过路径有两个函数可以得到 int stat(const char *path, struct stat *struct_stat); int lstat(const char *path,struct stat *struct_stat); 两个函数的第一个参数都是文件的路径,第二个参数是struct stat的指针。 返回值为0,表示成功执行。执行失败是,error被自动设置对应值。 这两个方法区别在于stat没有处理字符链接(软链接)的能力, 如果一个文件是符号链接,stat会直接返回它所指向的文件的属性; 而lstat返回的就是这个符号链接的内容。 (符号连接就是软连接,软链接的内容就是一个字符串。这个字符串就是它所链接的文件的绝对路径或者相对路径) 第二种通过文件描述符 int fstat(int fdp, struct stat *struct_stat);   *通过文件描述符获取文件对应的属性。fdp为文件描述符 */ struct stat st; /*公共网关接口(Common Gateway Interface,CGI)是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据API与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。格式化为HTML文档后,发送给浏览器,也可以将从浏览器获得的数据放到数据库中。*/ int cgi = 0; /* becomes true if server decides this is a CGI * program */ char *query_string = NULL; numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; while (!ISspace(buf[i]) && (i < sizeof(method) - 1)) { method[i] = buf[i]; i++; } j=i; method[i] = '\0'; //#include <strings.h> int strcasecmp(cost char*s1,const char* s2); //若参数s1s2字符串相等则返回0。s1大于s2则返回大于0 的值,s1 小于s2 则返回小于0的值。 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { unimplemented(client); return; } //get提交,提交的信息都显示在地址栏中。get提交,对于大数据不行,因为地址栏存储体积有限。 //post提交,提交的信息不显示地址栏中,显示在消息体中。post提交,可以提交大体积数据。 if (strcasecmp(method, "POST") == 0) cgi = 1; i = 0; while (ISspace(buf[j]) && (j < numchars)) j++; while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; //strcasecmp忽略大小写比较字符串 if (strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '\0')) query_string++; if (*query_string == '?') { cgi = 0; *query_string = '\0'; query_string++; headers(client, path); while (*query_string != '=') query_string++; printf("%s\n", query_string); query_string++; search_mysql(client, query_string); return; } } sprintf(path, "htdocs%s", url); if (path[strlen(path) - 1] == '/') //extern char *strcat(char *dest, const char *src); //把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest原来末尾的“\0”)。 strcat(path, "indexall.html"); //stat返回值: 成功返回0,返回-1表示失败 if (stat(path, &st) == -1) { //strcmp不忽略大小写比较字符串 //一直用get_line读取文件,读到http头结束 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); not_found(client); } //执行这一步代表读取到了这个文件 else { /* S_IFMT   0170000   文件类型的位遮罩   S_IFSOCK 0140000   套接字   S_IFLNK 0120000     符号连接   S_IFREG 0100000     一般文件   S_IFBLK 0060000     区块装置   S_IFDIR 0040000     目录   S_IFCHR 0020000     字符装置   S_IFIFO 0010000     先进先出 ​   S_ISUID 04000     文件的(set user-id on execution)位   S_ISGID 02000     文件的(set group-id on execution)位   S_ISVTX 01000     文件的sticky位 ​   S_IRUSR(S_IREAD) 00400     文件所有者具可读取权限   S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限   S_IXUSR(S_IEXEC) 00100     文件所有者具可执行权限 ​   S_IRGRP 00040             用户组具可读取权限   S_IWGRP 00020             用户组具可写入权限   S_IXGRP 00010             用户组具可执行权限 ​   S_IROTH 00004             其他用户具可读取权限   S_IWOTH 00002             其他用户具可写入权限   S_IXOTH 00001             其他用户具可执行权限 */ //是否是文件夹 if ((st.st_mode & S_IFMT) == S_IFDIR) strcat(path, "/myblog.html"); //文件所有者或文件所属组或其他人 具有可执行权限 if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) cgi = 1; if (!cgi) //执行这一步代表这个文件存在,但是不能执行 //于是就换成读取文件内容再发送 serve_file(client, path); else execute_cgi(client, path, method, query_string); } close(client); }
最新发布
08-01
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值