Dirty COW is the name for a vulnerability that stems from a race condition in the way that the Linux kernel's memory subsystem handles read only private mappings when a Copy On Write situation is triggered. Copy On Write allows for the sharing of resources rather than the duplication of them. If a resource is duplicated but not modified, it is not necessary to create a new resource - a copy is only triggered when a modification occurs to the shared resource, creating a unique version only when it is actually different. Under certain circumstances triggered by this race condition it is possible for an attacker using this vulnerability to modify the original rather than their own copy.
There's a nice video that does a great job walking you through the details of the bug and the initial exploit.
But, more interesting to me than a local privilege escalation, this is a bug in the Linux kernel, containers such as Docker wont save us.
To take advantage of this bug, we need a shared resource across all processes. Finding a likely candidate to do interesting things becomes the way forward here. Linux has a feature, "vDSO" (virtual dynamic shared object), which is a small shared library that the kernel automatically maps into the address space of all user-space applications. Sound useful? It exists because some system calls are used so frequently that invoking them the normal way can end up impacting the overall performance of the system dramatically - but it is also perfect for our nefarious needs.
So, we have some shared memory(library) across all processes that contains code that will be executed... what to do...
scumjr pulled together a POC that uses Dirty COW to modify the clock_gettime() function in the vDSO memory space. The vulnerability modifies the execution of this function for all the callers not just the running process. Once the race condition is triggered, and the shellcode executes, it will send you a root shell.
I set up a test environment in AWS, and you can as well. In the us-west-2 region, create an instance using the amazon ami with the id ami-7172b611, and for my example, I was running kernel 4.4.19-29.55.amzn1.x86_64, but any vulnerable kernel should suffice.
Docker files and such to create a container and deploy scumjr's exploit can be found on github.
I created a video, linked below, that walks you through the exploit. It begins by showing the OS version and docker version on the EC2 instance.
Then it starts a container running a shell. Once in the container, I edit the payload to send the shell back to the IP of the container, compile, and fire off the exploit.
Once I get a connection back, I demonstrate that I am the root user and that I see files on the host outside of those on the container.
Or video on youtube.
I'm particularly interested in demonstrating escaping out of docker simply because I think many people overestimate what is required. Kernel exploits are rarer than user space escalations, but they are not without example - and understanding that even with the separation offered by the kernel features that make containers possible, it's possible to get past them with a kernel exploit.
There are many more POC exploits using different strategies worth looking at. A comprehensive list is being built over at github.
vDSO is a performance optimization. The man page for vDSO calls out gettimeofday as an example, it is called often by both user-space applications and the C library. Think time-stamps or timing loops or polling—all of these frequently when a process needs to know what time it is right now. This isn't secret information, so, it can easily and safely be shared across all processes. The kernel is responsible for putting the time into a memory location any process can access, and through a function defined in the vDSO shared object, a process can access this information. In this way, gettimeofday changes from a more expensive system call to that of a normal function. Boom, everything is faster.
The exploit takes advantage of the fact that there is a shared memory range mapped into all processes that contains code for common functions that are called frequently. The exploit uses this Dirty COW write the payload into some unused space in vDSO memory and to change the function preamble to call this shellcode before doing its normal business.
The shellcode begins by checking if its being invoked by root. If so, it continues on, otherwise it returns and executes the regular clock_gettime. Next, it checks for the presence of the file "/tmp/.x". If it can get this file, then at this point, it knows it is root and it is the only tread running. Next up, the shellcode opens a reverse TCP connection serving up a shell to a hard-coded IP and port in the payload. By default, this is localhost, but in the case of containers it is useful to change this to the place we are actually running the exploit from. Lines 17 and 18 in the payload.s source file set up the ip and port for the reverse shell represented as little endian values:
IP equ 0x0100007f
PORT equ 0xd204
Update: There's been a patch that allows you to just specify the IP and port on the command line in the format "ip:port", so that is much nicer than messing around with this code!
Thanks to scumjr for the great POC Exploit, LiveOverflow for the great walk-through, and of course Phil Oester for finding the vulnerability.
Farm to table information security