Brains


on Security, Linux中账户安全机制

探索Linux的SUID位

在系统安全中,uid扮演了非常重要的角色,这里比较深入的叙述了Linux中的uid机制。

写在前面

本学期学习的信息安全课程中,接触到了Linux的uid方面的,关于访问权限控制的知识。由于从论文上得到的知识,总感觉有点虚幻,就想自己动手探索一下:Linux中uid的特性。以下实验均出自Ubuntu 12.04 64位机器。

ruid、euid、suid

一个程序,准确的说是一个进程,拥有着这三种id。它们分别代表着:

  • ruid : 哪个用户运行这个程序,ruid就是谁
  • euid : 代表着该进程的执行权限,每当进程访问一些资源时,内核就会根据进程的euid来判断该操作是否有权限执行
  • suid : 保存的uid的,通常与euid相同,当进程execve一个进程时,子进程的suid会保存euid字段,变成euid。而fork会把父进程的ruid、euid、suid完全的复制一份。

进程每次打开、创建或删除一个文件时,内核对文件访问权限的测试如下:

  • 若进程的euid为0(root用户),则允许访问
  • 若进程的euid等于文件的所有者id,那么如果文件的所有者适当的访问权限位被设置,则允许访问,否则拒绝访问。适当的访问权限指:若进程为而打开文件,那么用户写位置1,若进程为而打开文件,那么用户读文件位置1.
  • 若进程的有效组id或者进程的附属组id之一等于文件的组id,那么如果组适当的访问权限被设置,则允许访问,反之则拒绝访问。
  • 若其他用户适当的访问权限位被设置,则允许访问,否则拒绝访问。

设置权限

#include <unistd.h>
int setuid( uid_t uid );  
int setgid( gid_t gid );  

1)若进程具有root权限,即euid为0,那么setuid会将ruid、euid、suid全部设为uid参数。
2)若进程没有root权限,但uid参数等于ruid或者suid,那么setuid只将euid设置为uid参数。
3)如果上面两个条件都不满足,则errno设置为EPERM,并返回-1。

从这些特性中,可以看出,suid的出现,是可以恢复进程依据最小权限原则而临时去掉的权限的。

#include <unistd.h>
int seteuid( uid_t uid );  
int setegid( gid_t gid );  


1)一个root用户,可以将euid设置为任意的uid参数
2)非特权用户,只能将euid设置为ruid或者suid中的一个。

从这些特性中,进程不能通过seteuid操作来恢复之前的权限的。

综述:setuid可用于临时性的改变进程的权限,而seteuid用于永久的取得进程的某些权限。注意只有root用户执行setuid才改变suid,其他情况下setuid和seteuid均不改变suid.

关于文件的S位

我们知道文件通常有三种访问权限:r w x,但有时为了达到一些特定目的,给这个文件增加了一个s位。最典型的应用即Linux中,用户修改密码这个操作。保存密码的文件除了root用户,任何人不得访问的,但普通用户可以更改其密码,也就是说对这个密码文件具有可写的权限,这一操作能得以实现,主要归功于文件的s位。

s位的打开与关闭

可以使用chmod更改权限来打开s位,如下:

# chmod sxxx filename

s = 1:代表打开粘着位,关于粘着位放在后面讲。
s = 2:代表为群组打开s位
s = 4:代表为文件的使用者打开s位。

s 也可以是上述三个值的和,作用是它们功能的并集。

关于文件s位的作用

我的一个同学非常形象的描述这个s位的作用,它就像是一个印章(戳)。如果打开这个文件的s位,也就意味着无论谁执行使用这个文件,它都拥有着文件拥有者的权限。就好像银行行长给你一张支票,上面有他的签名。无论谁拿着这张支票都可以到银行取到钱。因为银行行长的签名具有向银行取钱的权利。

那么对于Linux的shadow文件或者是passwd文件,只有root可以读写。每一个用户可以通过passwd命令来更改自己的密码,为了实现普通用户可以更改仅root才能修改的shadow文件,操作系统为passwd的使用者位打开了s位。在Ubuntu 12.04 64上权限如下:

-rwsr-xr-x 1 root root 42824 Sep 13 2012 /usr/bin/passwd

关于文件粘着位(t)的理解

要删除一个文件,你不一定要有这个文件的写权限,但你一定要有这个文件的上级目录的写权限。也就是说,你即使没有一个文件的写权限,但你有这个文件的上级目录的写权限,你也可以把这个文件给删除,而如果没有一个目录的写权限,也就不能在这个目录下创建文件。

如何才能使一个目录既可以让任何用户写入文件,又不让用户删除这个目录下他人的文件,粘着位就是能起到这个作用。粘着位一般只用在目录上,也可作用在普通文件上。如果一个可执行程序文件这一位被设置了,那么当该程序第一次被执行时,在其终止时,程序的正文部分(机器指令)仍被保存在交换区,这使得下次执行该程序时能较快的将其载入内存

如果用户对目录有写权限,则可以删除其中的文件和子目录,即使该用户不是这些文件的所有者,而且也没有读或写许可。粘着位出现执行许可的位置上,用t表示,设置了该位后,其它用户就不可以删除不属于他的文件和目录。但是该目录下的目录不继承该权限,要再设置才可使用。

目录/tmp和/var/tmp是设置粘着位的典型候选者--任何人都可以在这两个目录中创建文件,但用户不能删除或重命名属于其他人的文件,为此在这两个目录的文件模式都设置了粘着位。其权限如下所示:

drwxrwxrwt 13 root root 4096 Jan 24 18:47 /tmp

注意:那么原来的执行标志x到哪里去了呢? 系统是这样规定的, 假如本来在该位上有x, 则这些特别标志 (suid, sgid, sticky) 显示为小写字母 (s, s, t). 否则, 显示为大写字母 (S, S, T) 。

测试程序

/*
 * set.c 文件 使用root权限执行的
 */  
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <sys/types.h>
  4 #include <unistd.h>
  5
  6 int main()
  7 {
  8         uid_t ruid;
  9         uid_t euid;
 10         uid_t suid;
 11         pid_t pid;
 12
 13         setresuid( 800, 900, 100 );
 14
 15         getresuid( &ruid, &euid, &suid );
 16         printf("orignal ruid = %d, euid = %d, suid = %d\n", ruid, euid, suid );
 17 #if 0
 18         if( (pid = fork()) == 0 )
 19         {
 20                 execve( "./hello",NULL, NULL );
 21         }
 22 #endif
 23         if( (pid = fork()) == 0 )
 24         {
 25                 getresuid( &ruid, &euid, &suid );
 26                 printf("fork ruid = %d, euid = %d, suid = %d\n", ruid, euid, suid );
 27         }
 28 #if 0
 29         seteuid(800);
 30         getresuid( &ruid, &euid, &suid );
 31         printf("seteuid(800) ruid = %d, euid = %d, suid = %d\n", ruid, euid, suid );
 32
 33         seteuid(900);
 34         getresuid( &ruid, &euid, &suid );
 35         printf("seteuid(900) ruid = %d, euid = %d, suid = %d\n", ruid, euid, suid );
 36 #endif
 37         return 0;
 38 }
/*
 * hello.c 文件
 */
  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4
  5 int main()
  6 {
  7         uid_t r_uid;
  8         uid_t e_uid;
  9         uid_t s_uid;
 10
 11         getresuid( &r_uid, &e_uid, &s_uid );
 12
 13         printf("hello --- r_uid = %d, e_uid = %d, s_uid = %d\n", r_uid, e_uid, s_uid )    ;
 14
 15         return 0;
 16 }

参考

[1] UNIX环境高级编程第4、8章
[2] 博客园的落崖惊风

EOF

comments powered by Disqus

纸上得来终觉浅,绝知此事要躬行~