linux进程的用户ID

linux进程的用户ID

1. 前言

最近关于”太极越狱的iOS 8.1.3-8.4含有重大安全后门,越狱后导致任意APP可以提权到Root”新闻在安全圈传的比较广,具体的一篇文章见:太极越狱重大安全后门。从这篇分析文章中看到主要就是因为太极越狱修改了setreuid这个系统调用,导致任意APP都可以通过setreuid(0,0)来获取root权限。

2. setreuid定义

#include <sys\types.h>
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
DESCRIPTION
    setreuid() sets real and effective user IDs of the calling process.

从setreuid的man手册中看到,setreuid就是设置进程的实际用户ID和有效用户ID。

3. 进程的用户ID

每一个进程都有三个用户相关的ID,实际UID、有效UID、保存的set-user-ID:

  • 进程的实际UID确定了进程所属的用户,也就是代表了这个进程的创建者ID。
  • 进程的有效用户ID是指进程目前实际有效的用户ID,euid主要用来校验权限,当我们要判断一个进程是否能够访问某一个资源的时候是用euid来进程判断而不是ruid。通常,实际用户ID和有效用户ID是一样的,但是如果二进制可执行文件设置了set-user-ID的话那么运行时进程的有效用户ID会被设置成可执行文件的所有者ID。
  • 保存的set-user-ID(也可以称之为保存用户ID)从字面上来看就是用来保存UID的,为什么还要有这么一个ID呢?答案我们在后面揭开。
/************************************************************************* > File Name: getreuid2.c > Author: lxg > Mail: [email protected] > Created Time: 2015年07月04日 星期六 12时55分14秒 ************************************************************************/

#define _GNU_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

int main(int argc, char *argv[])
{
    uid_t ruid, euid, suid;

    if(getresuid(&ruid, &euid, &suid) == -1)
    {
        fprintf(stderr, "getresuid error:%s.\n", strerror(errno));
        return 1;
    }
    printf("PID=%d, real uid=%ld, effective uid=%ld, save set-user-ID=%ld\n",
          getpid(), (long)ruid, (long)euid, (long)suid);

    return 0;
}

上面的程序输出进程的实际用户ID、有效用户ID、 保存set-user-ID。

root@lxg-X240:/home/lxg/station/TLPI/chapter_38# ./getreuid2
PID=5395, real uid=0, effective uid=0, save set-user-ID=0
root@lxg-X240:/home/lxg/station/TLPI/chapter_38# chmod u+s getreuid2
root@lxg-X240:/home/lxg/station/TLPI/chapter_38# ls -l
总用量 16
-rwsr-xr-x 1 root root 7624 7月 4 14:21 getreuid2
lxg@lxg-X240:~/station/TLPI/chapter_38$ ./getreuid2
PID=5808, real uid=1000, effective uid=0, save set-user-ID=0

可执行文件getreuid2的owner是root,第一次运行的时候ruid、euid、suid都是0,接着我设置了getreuid2的set-user-ID,当lxg执行getreuid2的时候看到ruid是1000(lxg),其它的euid、suid都是0。

4. 修改进程的用户ID

从上文我们知道进程的”能力”由进程的有效用户ID来决定的,不管进程的实际用户ID只要进程的有效用户ID为0那么这个进程就具有了root的权限,可以”为所欲为”。那么任何进程都能”自由的”修改自己的有效用户ID吗?答案肯定是:否。但是太极越狱修改后的setreuid API却具有这个能力,可以让任意进程随意的修改euid,这样就导致了任何APP都可以获取到root权限,在系统里面”为所欲为”。
首先我们进一步来了解一下保存set-user-ID,当执行程序时,将会依次发生如下事件。
1. 若可执行文件的set-user-ID权限位已开启,则将进程的有效用户ID设置为可执行文件的属主ID。若未设置set-user-ID权限位,则进程的有效用户ID将保持不变(跟进程的实际用户ID一致)。
2. 保存set-user-ID的值由对应的有效用户ID复制而来。无论正在执行的文件是否设置了set-user-ID,这一复制都将进行。
上面两点其实就是说:保存set-user-ID就是把进程的有效用户ID保存起来,为什么要将euid保存起来呢?因为有不少系统调用,允许将set-user-ID进程的有效用户ID在实际用户ID和保存set-user-ID之间切换。这也说明保存set-user-ID只对于设置了set-user-ID权限位的进程有用,因为只有这样的程序会出现有效用户ID和实际用户ID不一致。
suid就好像你的朋友给了你他的宿舍的钥匙,那么你可以凭钥匙在你自己的宿舍和他的宿舍之间自由的进入,但是一旦你自己把朋友宿舍的钥匙丢了那么你就失去了进入他宿舍的能力。
修改进程的有效用户ID的系统API有setuid、seteuid、setreuid,这三个函数对于特权进程(也就是说euid等于0)没有任何的限制,对于非特权进程(euid不等于0)只能在用户实际ID或者保存set-user-ID之间切换。太极越狱就是把非特权进程的这个限制打破了,所以导致了”安全性”问题。

/************************************************************************* > File Name: setreuid2.c > Author: lxg > Mail: [email protected] > Created Time: 2015年07月04日 星期六 18时30分45秒 ************************************************************************/

#define _GNU_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

void print_uid()
{
    uid_t ruid, euid, suid;

    if(getresuid(&ruid, &euid, &suid) == -1)
    {
        fprintf(stderr, "getresuid error:%s\n", strerror(errno));
        return;
    }

    printf("PID=%d, real UID=%ld, effective UID=%ld, save set-user-ID=%ld\n",
          getpid(), (long)ruid, (long)euid, (long)suid);
}

int main(int argc, char *argv[])
{
    uid_t ruid, euid, suid;

    if(getresuid(&ruid, &euid, &suid) == -1)
    {
        fprintf(stderr, "getresuid error:%s\n", strerror(errno));
        return 1;
    }

    print_uid();

    if(setreuid(-1, ruid) == -1)
    {
        fprintf(stderr, "seteuid error:%s\n", strerror(errno));
        return 1;
    }

    printf("Drop Privilege.\n");
    print_uid();

    if(setreuid(-1, suid) == -1)
    {
        fprintf(stderr, "seteuid error:%s\n", strerror(errno));
        return 1;
    }

    printf("Reacquire Privilege.\n");
    print_uid();

    return 0;
}

lxg@lxg-X240:~/station/TLPI/chapter_38$ ./setreuid2
PID=8512, real UID=1000, effective UID=1000, save set-user-ID=1000
Drop Privilege.
PID=8512, real UID=1000, effective UID=1000, save set-user-ID=1000
Reacquire Privilege.
PID=8512, real UID=1000, effective UID=1000, save set-user-ID=1000

上面是lxg用户执行setruid2的运行结果,我们看到期间进程的用户ID都是一样的没有发生任何的变化。

root@lxg-X240:/home/lxg/station/TLPI/chapter_38# chmod u+s ./setreuid2
lxg@lxg-X240:~/station/TLPI/chapter_38$ ls -l setreuid2
-rwsr-xr-x 1 root root 7724 7月 4 19:08 setreuid2

上面我们设置了setreuid2的set-user-ID权限位,接着我们再次在lxg用户下运行setreuid2。

lxg@lxg-X240:~/station/TLPI/chapter_38$ ./setreuid2
PID=8778, real UID=1000, effective UID=0, save set-user-ID=0
Drop Privilege.
PID=8778, real UID=1000, effective UID=1000, save set-user-ID=0
Reacquire Privilege.
PID=8778, real UID=1000, effective UID=0, save set-user-ID=0

我们看到进程的有效用户ID可以在实际用户ID和保存set-user-ID之间切换,对于set-user-ID的程序在编程的时候一定要注意把权限控制在最小的范围内,不需要特权的时候把进程的有效用户ID设置为用户的实际ID,只有在执行特定的操作的时候才需要特权把用户的有效用户ID设置成root。

5. 总结

上文中提到的进程的三个用户ID一般我们在set-user-ID程序中需要着重关注,非特权进程是无法将实际用户ID和有效用户ID设置为任意值的,有效用户ID是进程”能力”的体现,保存的set-user-ID是对你过去拥有的能力的一个存根。

你可能感兴趣的:(setreuid)