Linux 安全编程(一) 之 SUID 和 SGID


此文参考连接 http://forum.ubuntu.org.cn/weblog_entry.php?e=6900 且加上本人实际验证例子改编而成。

由于用户在UNIX下经常会遇到SUID、SGID的概念,而且SUID和SGID涉及到系统安全,所以用户也比较关心这个问题。

一、UNIX下关于文件权限的表示方法和解析

SUID 是 Set User ID, SGID 是 Set Group ID的意思。
UNIX下可以用ls -l命令来看到文件的权限。用ls命令所得到的表示法的格式是类似这样的:-rwxr-xr-x。
下面解析一下格式所表示的意思。
这种表示方法一共有十位: 9 8 7 6 5 4 3 2 1 0 - r w x r - x r - x

第9位表示文件类型,可以为p、d、l、s、c、b和-:
p表示命名管道文件;
d表示目录文件;
l表示符号连接文件;
-表示普通文件;
s表示socket文件;
c表示字符设备文件;
b表示块设备文件;

第8-6位、5-3位、2-0位分别表示文件所有者的权限,同组用户的权限,其他用户或组的权限,其形式为rwx:
r表示可读,可以读出文件的内容;
w表示可写,可以修改文件的内容;
x表示可执行,可运行这个程序;
没有权限的位置用-表示。
例子:
# ls -l /dev
crw-rw-r--    1 admin123 root      45, 255 Jul 29 01:42 isdninfo
crw-rw-r--    1 admin123 root       1,   2 Jul 29 01:42 kmem
lrwxrwxrwx    1 admin123 root           12 Jul 29 01:42 log -> /var/log/log
crw-rw-r--    1 admin123 root       1,   1 Jul 29 01:42 mem
brw-rw-r--    1 admin123 root      31,   0 Jul 29 01:42 mtdblock0
brw-rw-r--    1 admin123 root      31,   1 Jul 29 01:42 mtdblock1
#
# ls -l /var
srwxr-xr-x    1 admin123 root            0 Jul 29 22:30 rtpproxy.sock
drwxr-xr-x    2 admin123 root            0 Jul 29 22:30 run
-rw-r--r--    1 admin123 root            2 Jul 29 22:30 runhttpd
drwxr-xr-x    4 admin123 root            0 Jan  1  2011 samba
-rw-------    1 admin123 root         8192 Jan  1  2011 secrets.tdb
drwxr-x---    3 admin123 root            0 Jul 30 02:13 ser
prw-rw----    1 admin123 root            0 Jul 30 03:19 ser_fifo


[quagga@localhost socket]$ ls -l openfile.c
-rw-rw---- 1 zhang zhang  1770 2012-07-29 21:15 openfile.c
[zhang@localhost ctest]$ ls -l | grep socket
drwxr-xrwx  3 zhang zhang     4096 2012-07-31 10:13 socket

表示文件socket是目录文件,文件的所有者是zhang用户,而zhang用户属于zhang组,
文件只有1个硬连接,长度是4096个字节,最后修改时间2012-07-315。所有者zhang对文件有读写执行权限,
zhang组的成员对文件有读和执行权限, 其他的用户对这个文件读写执行权限。

如果一个文件被设置了SUID或SGID位,会分别表现在所有者或同组用户的权限的可执行位上。
例如:
1、-rwsr-xr-x 表示SUID和所有者权限中可执行位被设置
2、-rwSr--r-- 表示SUID被设置,但所有者权限中可执行位没有被设置
3、-rwxr-sr-x 表示SGID和同组用户权限中可执行位被设置
4、-rw-r-Sr-- 表示SGID被设置,但同组用户权限中可执行位没有被社置。

其实在UNIX的实现中,文件权限用12个二进制位表示,如果该位置上的值是1
表示有相应的权限: 11 10 9 8 7 6 5 4 3 2 1 0 S G T r w x r w x r w x
第11位为SUID位,第10位为SGID位,第9位为sticky位,第8-0位对应于上面三组rwx位。
11 10 9 8 7 6 5 4 3 2 1 0 上面的
-rwsr-xr-x的值为: 1 0 0 1 1 1 1 0 1 1 0 1
-rw-r-Sr--的值为: 0 1 0 1 1 0 1 0 0 1 0 0

给文件加SUID和SGID的命令如下:
chmod u+s filename 设置SUID位;
chmod u-s filename 去掉SUID设置;
chmod g+s filename 设置SGID位;
chmod g-s filename 去掉SGID设置;
另外一种方法是chmod命令用八进制表示方法的设置。如果明白了前面的12位 权限表示法也很简单。


二、SUID和SGID的详细解析

由于SUID和SGID是在执行程序(程序的可执行位被设置)时起作用,而可执行位只对普通文件和目录文件有意义,
所以设置其他种类文件的SUID和SGID位是没有多大意义的。首先讲普通文件的SUID和SGID的作用。
例子:

[quagga@localhost socket]$ ls -l mycat
-rwxrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
[quagga@localhost socket]$
[quagga@localhost socket]$ ls -l openfile.c
-rw-rw---- 1 zhang zhang  1770 2012-07-29 21:15 openfile.c

显然普通文件mycat和openfile.c是属于用户zhang的,任何用户都可以执行这个mycat程序,
quagga没有读openfile.c的权限(只有用户zhang以及zhang的同组成员有读openfile.c的权限)。

UNIX的内核是根据什么来确定一个进程对资源的访问权限的呢?

是这个进程的运行用户的(有效)ID,包括user id和group id。
用户可以用id命令来查到自己的或其他用户的user id和group id。

[quagga@localhost socket]$ id root
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
[quagga@localhost socket]$
[quagga@localhost socket]$ id zhang
uid=500(zhang) gid=500(zhang) groups=500(zhang)
[quagga@localhost socket]$
[quagga@localhost socket]$ id quagga
uid=501(quagga) gid=501(quagga) groups=501(quagga)
[quagga@localhost socket]$ 

除了一般的user id 和group id外,还有两个称之为effective的id,就是有效id,
上面的四个id表示为:uid,gid,euid,egid。

内核主要是根据euid和egid来确定进程对资源的访问权限。
一个进程如果没有SUID或SGID位,则euid=uid egid=gid,分别是运行这个程序的用户的uid和gid。

[quagga@localhost socket]$ ls -l openfile.c
-rw-rw---- 1 zhang zhang  1770 2012-07-29 21:15 openfile.c
[quagga@localhost socket]$
[quagga@localhost socket]$ ls -l mycat
-rwxrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
[quagga@localhost socket]$
[quagga@localhost socket]$ which cat
/bin/cat
[quagga@localhost socket]$ ls -l /bin/cat
-rwxr-xr-x 1 root root  55184 2009-03-02 21:57 cat
[quagga@localhost socket]$
[quagga@localhost socket]$ cat openfile.c
cat: openfile.c: Permission denied
[quagga@localhost socket]$
[quagga@localhost socket]$ ./mycat openfile.c
cannot open openfile.c[quagga@localhost socket]$ 

用户quagga的uid和gid分别为501和501,用户zhang的uid和gid为500和500,
由于mycat/cat程序没有设置SUID或SGID位,quagga运行mycat程序形成的进程的euid=uid=501,egid=gid=501,
内核根据这些值来判断进程对资源访问的限制,其实就是用户quagga对资源访问的权限,和zhang没关系。
(quagga对openfile.c没有读权限, 所以 mycat/cat程序去读openfile.c时会cannot open openfile.c/openfile.c: Permission denied,
尽管用户zhang以及zhang的同组成员有这个权限)

如果一个程序设置了SUID("chmod u+s mycat"),则euid和egid变成被运行的程序的所有者(zhang)的uid和gid,
例如quagga用户运行mycat,euid=500,egid=500,uid=501,gid=501,

[zhang@localhost socket]$ ls -l openfile.c
-rw-rw---- 1 zhang zhang 1770 2012-07-29 21:15 openfile.c
[zhang@localhost socket]$
[zhang@localhost socket]$ ls -l mycat
-rwxrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
[zhang@localhost socket]$
[zhang@localhost socket]$ chmod u+s mycat
[zhang@localhost socket]$
[zhang@localhost socket]$ ls -l mycat
-rwsrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
[zhang@localhost socket]$ 

则用户quagga运行这个进程,quagga具就具有它的属主zhang(uid=500)的资源访问权限(读openfile.c的权限)。

[quagga@localhost socket]$ ./mycat openfile.c
[openfile.c-43]: sockfd[1]=4, fd=3
[my_open-78]: sockfd[0]=3, sockfd[1]=4, fd=4, c=x
/*
* this program is excuted by program of "mycat",
* execl("./openfile", "openfile", argsockfd, pathname, argmode, (char *) NULL);
*
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd);

int main(int argc, char **argv)
{
    int fd = -1, n = 0;
    char buf[10240] = {0};
    
    if (argc != 4)
    {
        fprintf(stderr, "openfile <sockfd#> <filename> <mode>\n");
        exit(255);
    }

    if ( (fd = open(argv[2], atoi(argv[3]))) < 0 )
    {
        exit( (errno > 0) ? errno : 255 );
    }

    /*
    n = read(fd, buf, sizeof(buf));
    write(STDOUT_FILENO, buf, n);
    */
    
    if ( write_fd(atoi(argv[1]), "x", 1, fd) < 0 )
    // if ( write(atoi(argv[1]), &fd, sizeof(int)) < 0 )
    {
        exit( (errno > 0) ? errno : 255 );
    }
    
    fprintf(stderr, "[%s-%d]: sockfd[1]=%d, fd=%d\n", __FILE__, __LINE__, atoi(argv[1]), fd );
    
    exit(0);
}

ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
    struct msghdr msg;
    struct iovec    iov[1];

#if 1
    union
    {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    
    struct cmsghdr *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int *) CMSG_DATA(cmptr)) = sendfd;
#else
    msg.msg_accrights = (caddr_t) &sendfd;
    msg.msg_accrightslen = sizeof(int);
#endif

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    return(sendmsg(fd, &msg, 0));
}
[quagga@localhost socket]$ 

SUID的作用就是这样:让本来没有相应权限的用户运行这个程序时,可以访问他没有权限访问的资源。

passwd就是一个很鲜明的例子。
SUID的优先级比SGID高,当一个可执行程序设置了SUID,则SGID会自动变成相应的egid。

下面讨论一个例子:
UNIX系统有一个/dev/kmem的设备文件,是一个字符设备文件,里面存储了核心程序要访问的数据,包括用户的口令。
所以这个文件不能给一般的用户读写,权限设为:

# ls -l /dev/kmem

cr--r----- 1 root system 2, 1 May 25 1998 kmem
但ps等程序要读这个文件,而ps的权限设置如下:
# ls -l /bin/ps

-r-xr-sr-x 1 bin system 59346 Apr 05 1998 ps
这是一个设置了SGID的程序,而ps的用户是bin,不是root,所以不能设置SUID来访问kmem,但大家注意了,
bin和root都属于system组,而且ps设置了SGID,一般用户执行ps,就会获得system组用户的权限,而文件kmem的
同组用户的权限是可读,所以一般用户执行ps就没问题了。但有些人说,为什么不把ps程序设置为root用户的程序,
然后设置SUID位,不也行吗?这的确可以解决问题,但实际中为什么不这样做呢?
因为SGID的风险比SUID小得多,所以出于系统安全的考虑,如果可能的话,应该尽量用SGID代替SUID的程序。

下面来说明一下SGID对目录的影响。
如果一个目录设置了SGID位,那么某用户(此用户对这个目录有写权限的话)在这个目录所建立的文件的组
都会自动转为这个目录的属主所在的组,而文件所有者不变,还是属于建立这个文件的用户。

[zhang@localhost ctest]$ whoami
zhang
[zhang@localhost ctest]$ chmod o+w socket
[zhang@localhost ctest]$ ls -l | grep socket
drwxr-xrwx  3 zhang zhang     4096 2012-07-31 10:12 socket
[zhang@localhost ctest]$ chmod g+s socket
[zhang@localhost ctest]$
[zhang@localhost ctest]$ ls -l | grep socket
drwxr-srwx  3 zhang zhang     4096 2012-07-31 10:13 socket

[quagga@localhost socket]$ whoami
quagga
[quagga@localhost socket]$ touch quagga
[quagga@localhost socket]$ ls -l quagga
-rw-rw-r-- 1 quagga zhang 0 2012-07-31 11:56 quagga
[quagga@localhost socket]$ 

如果一个目录没有设置SGID位,那么某用户(此用户对这个目录有写权限的话)在这个目录下所建立文件的组
还是建立这个文件的用户所在的组,文件所有者还是属于建立这个文件的用户。

[zhang@localhost ctest]$ whoami
zhang
[zhang@localhost ctest]$ chmod g-s socket
[zhang@localhost ctest]$ ls -l | grep socket
drwxr-xrwx  3 zhang zhang     4096 2012-07-31 11:56 socket
[zhang@localhost ctest]$

[quagga@localhost socket]$ whoami
quagga
[quagga@localhost socket]$ touch quagga1
[quagga@localhost socket]$ ls -l quagga*
-rw-rw-r-- 1 quagga zhang  0 2012-07-31 11:56 quagga
-rw-rw-r-- 1 quagga quagga 0 2012-07-31 11:57 quagga1
[quagga@localhost socket]$
[quagga@localhost socket]$ 


附录:

mycat.c 源码:

/*

* file name: mycat.c
* this program is from <unix network program(volume 1 third edition)> 15.7 "unix domain socket"
*/

#include <stdio.h>
#include <stdlib.h>     // for exit
#include <unistd.h>     // for write
#include <sys/wait.h>   // for WEXITSTATUS
#include <sys/un.h>     // for socketpair
#include <sys/socket.h> // for socketpair
#include <sys/types.h>
#include <sys/stat.h>   //for open
#include <fcntl.h>
#include <errno.h>

int my_open(const char *pathname, int mode);
ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd);

int main(int argc, char **argv)
{
        int             fd, n;
        char    buf[10240];

        if (argc != 2)
        {
                fprintf(stderr, "usage: mycat <pathname>.\n");
                return 0;
        }

        if ( (fd = my_open(argv[1], O_RDONLY)) < 0)
        {
                fprintf(stderr, "cannot open %s", argv[1]);
                return 0;
        }

        // while ( (n = read(fd, buf, sizeof(buf))) > 0)
        if( (n = read(fd, buf, sizeof(buf))) > 0)
        {
                write(STDOUT_FILENO, buf, n);
        }
        else
        {
                perror("read\n");
        }

        exit(0);
}

int my_open(const char *pathname, int mode)
{
        int                     fd, sockfd[2], status;
        pid_t           childpid;
        char            c, argsockfd[10], argmode[10];

        socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);

        if ( (childpid = fork()) == 0) /* child process */
        {
                close(sockfd[0]);
                snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);
                snprintf(argmode, sizeof(argmode), "%d", mode);
                execl("./openfile", "openfile", argsockfd, pathname, argmode, (char *) NULL);
                fprintf(stderr, "execl error.\n");
        }

        /* parent process */
        close(sockfd[1]);

        /* wait for the child to terminate */
        waitpid(childpid, &status, 0);

        if (WIFEXITED(status) == 0)
        {
                fprintf(stderr, "child did not terminate.\n");
                return 0;
        }

        if ( (status = WEXITSTATUS(status)) == 0)
        {
                read_fd(sockfd[0], &c, 1, &fd);
                //read(sockfd[0], &fd, sizeof(int));
                fprintf(stderr, "[%s-%d]: sockfd[0]=%d, sockfd[1]=%d, fd=%d, c=%c\n", __FUNCTION__, __LINE__, sockfd[0], sockfd[1], fd, c );

        }
        else
        {
                errno = status;         /* set errno value from child's status */
                fd = -1;
        }

        close(sockfd[0]);
        return(fd);
}

ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
        struct msghdr msg;
        struct iovec iov[1];
        ssize_t n;

#if 1
        union
        {
                struct cmsghdr cm;
                char control[CMSG_SPACE(sizeof(int))];
        } control_un;

        struct cmsghdr *cmptr;

        msg.msg_control = control_un.control;
        msg.msg_controllen = sizeof(control_un.control);
#else
        int                             newfd;

        msg.msg_accrights = (caddr_t) &newfd;
        msg.msg_accrightslen = sizeof(int);
#endif

        msg.msg_name = NULL;
        msg.msg_namelen = 0;

        iov[0].iov_base = ptr;
        iov[0].iov_len = nbytes;
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;

        if ( (n = recvmsg(fd, &msg, 0)) <= 0)
                return(n);

#if 1
        if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(int)))
        {
                if (cmptr->cmsg_level != SOL_SOCKET)
                {
                        fprintf(stderr, "control level != SOL_SOCKET\n");
                }

                if (cmptr->cmsg_type != SCM_RIGHTS)
                {
                        fprintf(stderr, "control type != SCM_RIGHTS\n");
                }
                *recvfd = *((int *) CMSG_DATA(cmptr));
        }
        else
        {
                *recvfd = -1;           /* descriptor was not passed */
        }
#else
/* *INDENT-OFF* */
        if (msg.msg_accrightslen == sizeof(int))
                *recvfd = newfd;
        else
                *recvfd = -1;           /* descriptor was not passed */
/* *INDENT-ON* */
#endif

        return(n);
}


openfile.c源码:

/*

* file name: openfile.c
* this program is excuted by program of "mycat",
* execl("./openfile", "openfile", argsockfd, pathname, argmode, (char *) NULL);
*
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd);

int main(int argc, char **argv)
{
        int fd = -1, n = 0;
        char buf[10240] = {0};

        if (argc != 4)
        {
                fprintf(stderr, "openfile <sockfd#> <filename> <mode>\n");
                exit(255);
        }

        if ( (fd = open(argv[2], atoi(argv[3]))) < 0 )
        {
                exit( (errno > 0) ? errno : 255 );
        }

        /*
        n = read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, n);
        */

        if ( write_fd(atoi(argv[1]), "x", 1, fd) < 0 )
        // if ( write(atoi(argv[1]), &fd, sizeof(int)) < 0 )
        {
                exit( (errno > 0) ? errno : 255 );
        }

        fprintf(stderr, "[%s-%d]: sockfd[1]=%d, fd=%d\n", __FILE__, __LINE__, atoi(argv[1]), fd );

        exit(0);
}

ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
        struct msghdr msg;
        struct iovec    iov[1];

#if 1
        union
        {
                struct cmsghdr cm;
                char control[CMSG_SPACE(sizeof(int))];
        } control_un;

        struct cmsghdr *cmptr;

        msg.msg_control = control_un.control;
        msg.msg_controllen = sizeof(control_un.control);

        cmptr = CMSG_FIRSTHDR(&msg);
        cmptr->cmsg_len = CMSG_LEN(sizeof(int));
        cmptr->cmsg_level = SOL_SOCKET;
        cmptr->cmsg_type = SCM_RIGHTS;
        *((int *) CMSG_DATA(cmptr)) = sendfd;
#else
        msg.msg_accrights = (caddr_t) &sendfd;
        msg.msg_accrightslen = sizeof(int);
#endif

        msg.msg_name = NULL;
        msg.msg_namelen = 0;

        iov[0].iov_base = ptr;
        iov[0].iov_len = nbytes;
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;

        return(sendmsg(fd, &msg, 0));
}

//todo



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值