Set-UID

1. Task 1

先使用 env 查看环境变量如下图所示

再使用 export MY123=456 设置环境变量MY123为456,如下

此时查看环境变量,可以找到名为MY123的环境变量

再运行unset MY123进行清除

此时查看环境变量,已经找不到了名为 MY123 的环境变量

原因分析: export 和 unset 都是shell自身的命令,操作的是shell变量,而使用export命令设置的shell变量会被shell传递到子进程中, 所以shell fork得到的子进程env 能看到export命令设置的环境变量,而unset清除之后就不会再传递过去,所以就看不到了。

 

2. Task 2

示例代码如下:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
extern char **environ;
 
void printenv() {
        int i = 0;
        while (environ[i] != NULL) {
                printf("%s\n", environ[i]);
                i++;
        }
}
 
void main() {
        pid_t childPid;
        switch(childPid = fork()) {
                case 0: /* child process */
                        printenv();  // 标记1
                        exit(0);
                default: /* parent process */
                        //printenv();  // 标记2
                        exit(0);
        }
}

编译并运行,结果如下:

step2 中 将 标记1 和 标记 2中的语句换过来,再编译查看结果

可以看到两次都输出了一大长串环境变量,仔细一看还是有一些不一样的,比如输出的最后一个环境变量。使用 diff 命令查看两个输出文件差异如下:

可以看到刚好是最后一个环境变量不同,为各自的文件名,这是因为两次运行的程序文件名不一样,所以这个环境变量不一样。

接下来我们试着编译成同一个程序名,还是按之前的顺序操作,再用diff查看两个输出文件差异,如下:

原因分析:看到两次输出完全一样,一次是父进程的输出,一次是子进程输出。说明在Fork后,父子进程有着相同的环境变量。这是 Fork 本身的特性导致的,Fork 后的父子进程除了返回值不一样以外基本所有东西都一样,包括栈和堆,且修改互不影响,但描述符是共享的,修改会相互影响。环境变量开始时存放在栈上,后面有可能到堆上,但这两部分父子进程内容都是一样的,因此父子进程打印环境变量的内容是一样的。

 

3. Task 3

示例代码如下:

#include <stdio.h>
#include <stdlib.h>
 
extern char **environ;
 
int main() {
        char *argv[2];
        argv[0] = "/usr/bin/env";
        argv[1] = NULL;
        execve("/usr/bin/env", argv, NULL);
        return 0;
}

step1:编译并查看输出

可以看到并没有环境变量的输出

step2:修改execve那行代码为  execve("/usr/bin/env", argv, environ),编译运行查看结果

可以看到这一次输出了一长串的环境变量

step3:

新程序如何获取环境变量: 通过execve等函数运行新程序,可以在参数里指定环境变量指针来对新程序设置环境变量,这也正是step1 和 step2结果有差异的原因,step1设置新程序环境变量指针为空,所以新程序没有环境变量,也就没有输出; step2中设置了环境变量,所以新程序有环境变量,所以有输出。

 

4. Task 4

示例代码如下:

#include <stdio.h>
#include <stdlib.h>
 
extern char **environ;
 
int main() {
        char *argv[2];
        argv[0] = "/usr/bin/env";
        argv[1] = NULL;
        execve("/usr/bin/env", argv, NULL);
        return 0;
}

编译并运行,结果如下:

可以看到输出了一长串环境变量。

原因分析:system函数是通过运行 shell 程序来执行命令的,而命令 env输出了一长串环境变量,这说明环境变量从调用者传给了shell程序,再从shell程序传给了子进程 env,所以才会显示这么多环境变量。

 

5. Task 5

step 1:示例代码如如下

#include <stdio.h>
#include <stdlib.h>
 
extern char **environ;
 
void main() {
    int i = 0;
    while (environ[i] != NULL) {
        printf("%s\n", environ[i]);
        i++;
    }
}

step 2:编译,并将用户改成 root,设置 set-uid

可以看到已经把用户修改成root了,并且set-uid已经设置好了

step 3:修改环境变量PATH, LD_LIBRARY_PATH 和 ANY_NAME, 其中前两个是已存在的,第三个是我们自定义的。

先查下一下这几个环境变量的值

接下来进行修改,由于PATH, LD_LIBRARY_PATH是已存在的,我们只向其中添加一部分内容,这里我们向两个环境添加一个名为 /tmp 的路径。

运行上面编译好的程序,输出如下

进一步筛选一下

分析:可以看到输出的环境变量中包含PATH 和 ANY_NAMEANY_NAME,且环境变量PATH为我们修改后的值。但其中没有我们用 export 命令设置的环境变量LD_LIBRARY_PATH。这个出乎意外,按道理说 用 export 设置的都会进入子程序的环境变量。其实这是一种动态链接器的保护策略,因为这个程序用到了动态链接库。

因为我们是在普通用户的状态下修改 LD_LIBRARY_PATH,如果这个修改能传递到 set-uid 程序中,那么我们就能通过修改 LD_LIBRARY_PATH 来改变 set-uid程序的库的寻找位置来实施攻击。因此动态链接器在 effective uid 和 real uid不一致 或者 effective groupid 和 real groupid不一致时,会将 LD_LIBRARY_PATH 和 LD_PRELOAD 环境变量忽略掉,所以子进程是看不到的。而其他的环境变量动态链接器没有使用到,因此也不会去干涉,所以子进程能够看到。

 

6. Task 6

示例代码如下:

int main() {
    system("ls");
    return 0;
}

编译并将用户改成 root, 设置 set-uid

再将程序 id 拷贝到 /home/seed/目录下,并重命名为 ls

最后修改环境变量PATH, 添加目录 /home/seed

此时运行上面示例代码编译的程序,结果如下:

可以看到 原本应该执行的是命令 ls,打印文件夹内容和信息,但是由于我们修改了PATH,并提供了一个假冒的程序 ls,实际上是程序 id,所以示例代码实际执行的是程序

id,程序 id 是 set-uid 程序运行的,按理说应该也有 root 权限,但从程序 id 的输出来看,其并没有获得 root 权限。这是因为我用的是 Ubuntu 16.04 VM,其默认的 shell 是 /bin/dash。 Ubuntu 16.04 的 dash 中有一个对抗攻击措施,当 dash 发现自己自己在一个 set-uid程序中执行时,它会把 effective user-id 改成 real user-id, 也就是放弃了特权,所以攻击失败。所以这里先将默认 shell 更改成 zsh,它没有这种防御措施。

再次运行示例程序,可以发现,成功获取 root 权限。同理我们可以将其他的程序伪装成 ls 让 set-uid 示例程序去执行。

原因分析:根本在于 system 函数是调用 shell 去执行命令,而 shell 依赖于各种环境变量,这些环境环境变量普通用户可以修改,进而可以影响到 set-uid程序的执行状态,从而产生漏洞。

 

7. Task 7

step 1:示例代码如下

#include <stdio.h>
void sleep (int s) {
    /* If this is invoked by a privileged program, 
        you can do damages here! */
    printf("I am not sleeping!\n");
}

将上述代码编译成动态链接库,并配置 LD_PRELOAD 环境变量

编译以下代码

#include <stdio.h>
void sleep (int s) {
    /* If this is invoked by a privileged program, 
        you can do damages here! */
    printf("I am not sleeping!\n");
}

step 2:

1. myprog 现在是普通程序,用普通用户运行,结果如下,未等待一秒,而是输出一句话

2. 设置 myprog 为 root用户, set-uid程序,普通用户运行,结果如下,等待一秒后结束,无输出。

3. 设置 myprog 为 root用户, set-uid程序,使用 root 用户设置 LD_PRELOAD 环境变量,使用 root 用户运行myprog。结果如下,未等待一秒,而是输出一句话

4. 将 myprog 改成 set-uid user1程序,并在 user2 账号里设置 LD_PRELOAD 环境变量并运行。这里我新创建了账户temp,user1=temp, user2=seed。结果如下,

可以看到等待一秒后结束,无输出。

step 3:

原因分析:这个关键在于 LD_PRELOAD 环境变量有没有被动态连接器屏蔽,LD_PRELOAD 和 LD_LIBRARY_PATH 类似,具体机制可以查看上面 Task 5 及其分析。

对于第一种情况,effective uid 等于 real uid,均为 seed,LD_PRELOAD 环境变量没有被屏蔽,所以链接的是 libmylib.so.1.0.1,输出一句话

对于第二种情况,effective uid为root,real uid为seed,不相同,LD_PRELOAD 环境变量被屏蔽,从标准路径查找链接库,所以链接的是标准库。所以结果为等待一秒后结束,无输出。

对于第三种情况,effective uid 等于 real uid,均为 root,LD_PRELOAD 环境变量没有被屏蔽,所以链接的是 libmylib.so.1.0.1,输出一句话

对于第四种情况,effective uid为temp,real uid为seed,不相同,LD_PRELOAD 环境变量被屏蔽,从标准路径查找链接库,所以链接的是标准库。所以结果为等待一秒后结束,无输出。

 

8. Task 8

step 1:示例代码如下

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char *argv[]) {
    char *v[3];
    char *command;
    if(argc < 2) {
        printf("Please type a file name.\n");
        return 1;
    }
 
    v[0] = "/bin/cat"; v[1] = argv[1]; v[2] = NULL;
    command = malloc(strlen(v[0]) + strlen(v[1]) + 2);
    sprintf(command, "%s %s", v[0], v[1]);
 
    // Use only one of the followings. 
    system(command);
    // execve(v[0], v, NULL);
    return 0;
}

编译并设置为 root set-uid程序,正常运行没有什么问题

这里演示实施一个删除文件攻击, 用 root 账户在 /root 目录下 新建一个文件 tempfile, 然后切换到 seed 账户,可以看到无法访问也无法删除该文件

普通用户只要运行 ./task8 "./task8.c; rm /root/tempfile" 即可删除刚才的文件,从下面结果可以看出,该文件已经成功被删除。

step 2:将 step 1 中的调用 system 函数 改成调用 execve。重复上面的流程,可以发现,运行报错并且未删除该文件

原因分析:在使用 system函数时,最终是 shell 去执行命令,而且未仔细检查用户输入,使得其执行了两条命令。其中第二条命令为恶意构造的命令,删除了文件。改成使用 execve 之后,这种攻击方式是不会成功的,因为它是通过系统调用的方式去执行,只能执行一个进程,且进程名已指定,不会产生这种漏洞。

 

 

9. Task 9

示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
 
void main() {
    int fd;
 
    /* Assume that /etc/zzz is an important system file,
     * and it is owned by root with permission 0644.
     * Before running this program, you should creat 
     * the file /etc/zzz first. */
 
    fd = open("/etc/zzz", O_RDWR | O_APPEND);
    if (fd == -1) {
        printf("Cannot open /etc/zzz\n");
        exit(0);
    }

先用 root 权限创建 /etc/zzz 文件。然后编译上面代码,并设置为 root set-uid程序。使用普通账户运行该程序发现等了一秒,无输出。查看 /etc/zzz 可以发现文件内容已经被修改。

原因分析:这个特权程序打开了一个重要的的系统文件,并且在放弃特权时没有关闭该文件,而后调用Fork,子进程会继承这个文件描述符,造成特权泄露。子进程可以通过泄露的文件描述符向文件写入恶意内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值