Skip to content

Commit

Permalink
Updated check method to account for backports
Browse files Browse the repository at this point in the history
  • Loading branch information
jheysel-r7 committed Feb 28, 2024
1 parent 69b566c commit 6589b86
Showing 1 changed file with 67 additions and 26 deletions.
93 changes: 67 additions & 26 deletions modules/exploits/linux/local/runc_cwd_priv_esc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def initialize(info = {})
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'SickMcNugget', # Check method enhancements
'jheysel-r7', # Check method enhancements
'Rory McNamara' # Discovery
],
'Platform' => [ 'linux' ],
Expand All @@ -40,6 +42,8 @@ def initialize(info = {})
'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'],
[ 'URL', 'https://security-tracker.debian.org/tracker/CVE-2024-21626'],
[ 'URL', 'https://ubuntu.com/security/CVE-2024-21626'],
[ 'CVE', '2024-21626']
],
'DisclosureDate' => '2024-01-31',
Expand Down Expand Up @@ -71,10 +75,6 @@ def base_dir
def check
sys_info = get_sysinfo

unless sys_info[:distro] == 'ubuntu' || sys_info[:distro] == 'debian'
return CheckCode::Safe('Check method only available for Debian/Ubuntu systems')
end

# Make sure both docker and runc are present
unless command_exists?('runc')
return CheckCode::Safe('The runc command was not found on this system')
Expand All @@ -84,32 +84,73 @@ def check
return CheckCode::Safe('The docker command was not found on this system')
end

# 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>

# Keep sane check for Ubuntu
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

# These tokens break Rex::Version comparisons.
# Some distro runc packages use them for delimiting.
bad_tokens = ['+', '~']
bad_tokens.each do |token|
if package.include?(token)
package = package.split(token).first
minimum_version = "1.0.0"
version_info = cmd_exec('runc --version')

case sys_info[:distro]
when 'ubuntu'
version_info =~ /runc version\s+(\d+\S*)/
unfiltered_version = Regexp.last_match(1)

# https://ubuntu.com/security/CVE-2024-21626
if sys_info[:distro] == 'ubuntu'
if sys_info[:version].include? '23.10' # mantic
fixed_version = '1.1.7-0ubuntu2.2'
elsif sys_info[:version].include? '23.04' # lunar
fixed_version = '1.1.7-0ubuntu1'
elsif sys_info[:version].include? '22.10' # kinetic
fixed_version = '1.1.7-0ubuntu1'
elsif sys_info[:version].include? '22.04' # jammy
fixed_version = '1.1.7-0ubuntu1~22.04.2'
elsif sys_info[:version].include? '20.04' # focal
fixed_version = '1.1.7-0ubuntu1~20.04.2'
elsif sys_info[:version].include? '18.04' # bionic
fixed_version = '1.1.4-0ubuntu1~18.04.2+esm1'
elsif sys_info[:version].include? '16.04' # xenial
return CheckCode::Safe("Ubuntu version not affected")
elsif sys_info[:version].include? '14.04' # trusty
return CheckCode::Detected("Patch for this Ubuntu version was ignored. (end of standard support)")
else
fixed_version = '1.1.12'
end
# Replace any "+esm", "ubuntu", "~" or "-" with a "."
fixed_version = fixed_version.gsub(/\+[a-zA-Z]+/, '.').gsub(/ubuntu/, '.').gsub(/-/, '.').gsub(/~/, '.')
runc_version = unfiltered_version.gsub(/\+[a-zA-Z]+/, '.').gsub(/ubuntu/, '.').gsub(/-/, '.').gsub(/~/, '.')
end
when 'debian'

# The command runc --version returns a number of different fields, ex:
# "runc version 1.1.5+ds1\ncommit: 1.1.5+ds1-1+deb12u1\nspec: 1.0.2-dev\ngo: go1.19.8\nlibseccomp: 2.5.4"
#
# For Debian the 'version' field doesn't always provide enough accuracy (particularly for Buster) to determine
# whether or not the version is vulnerable which is why we extract the 'commit' field as it provides more detail.

version_info =~ /commit:\s+(\d+\S*)/
unfiltered_version = Regexp.last_match(1)

# https://security-tracker.debian.org/tracker/CVE-2024-21626
if sys_info[:version].include? '13' # Trixie (unstable at time of writing 2024-02-28)
fixed_version = '1.1.12+ds1-1'
elsif sys_info[:version].include? '12' # Bookworm
fixed_version = '1.1.5+ds1-1+deb12u1'
elsif sys_info[:version].include? '11' # Bullseye
fixed_version = '1.0.0~rc93+ds1-5+deb11u3'
elsif sys_info[:version].include? '10' # Buster
fixed_version = '1.0.0~rc6+dfsg1-3+deb10u3'
else
fixed_version = '1.1.12'
end
# Replace any "+deb", "+ds", "~rc", "u" or "-" with a "."
fixed_version = fixed_version.gsub(/(?:\+|~)[a-zA-Z]+/, '.').gsub(/u/, '.').gsub('-', '.')
runc_version = unfiltered_version.gsub(/(?:\+|~)[a-zA-Z]+/, '.').gsub(/u/, '.').gsub('-', '.')
else
return CheckCode::Safe('Check method only available for Debian/Ubuntu systems')
end

# From testing, 1.0.0-rc93 < all([1.0.0-rc94, 1.0.0, 1.1.0-rc.1, 1.1.0, 1.1.11])
if Rex::Version.new(package) >= Rex::Version.new('1.0.0-rc93') && Rex::Version.new(package) <= Rex::Version.new('1.1.11')
return CheckCode::Appears("Vulnerable runc version #{package} detected")
if Rex::Version.new(runc_version) < Rex::Version.new(fixed_version) && Rex::Version.new(runc_version) >= Rex::Version.new(minimum_version)
return CheckCode::Appears("Version of runc detected appears to be vulnerable: #{unfiltered_version}.")
end

CheckCode::Safe("runc #{package} is not vulnerable")
CheckCode::Safe("runc version #{unfiltered_version} is not vulnerable.")
end

def exploit
Expand Down

0 comments on commit 6589b86

Please sign in to comment.