CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/core/auxiliary/mikrotik.rb
Views: 1904
1
# -*- coding: binary -*-
2
3
module Msf
4
###
5
#
6
# This module provides methods for working with Mikrotik equipment
7
#
8
###
9
module Auxiliary::Mikrotik
10
include Msf::Auxiliary::Report
11
12
# this handles `export` (default), `export compact`, `export terse` and `export verbose`
13
# the format is a header line: `/ tree navigation`
14
# followed by commands: `set thing value`
15
def export_to_hash(config)
16
return {} unless config.is_a? String
17
18
config = config.gsub(/^\s{2,4}/, '') # replace code indents
19
config = config.gsub(/\\\s*\n/, '') # replace verbose multiline items as single lines, similar to terse
20
output = {}
21
header = ''
22
config.each_line do |line|
23
line = line.strip
24
# # jul/16/2020 14:26:57 by RouterOS 6.45.9
25
# typically the first line in the config
26
if %r{^# \w{3}/\d{2}/\d{4} \d{2}:\d{2}:\d{2} by (?<os>\w+) (?<version>[\d\.]+)$} =~ line
27
output['OS'] = ["#{os} #{version}"]
28
29
# terse format format is more 'cisco'-ish where header and setting is on one line
30
# /interface ovpn-client add connect-to=10.99.99.98 mac-address=FE:45:B0:31:4A:34 name=ovpn-out1 password=password user=user
31
# /interface ovpn-client add connect-to=10.99.99.98 mac-address=FE:45:B0:31:4A:34 name=ovpn-out2 password=password user=user
32
elsif %r{^(?<section>/[\w -]+)} =~ line && (line.include?(' add ') || line.include?(' set '))
33
[' add ', ' set '].each do |div|
34
next unless line.include?(div)
35
36
line = line.split(div)
37
if output[line[0].strip]
38
output[line[0].strip] << "#{div}#{line[1]}".strip
39
next
40
end
41
output[line[0].strip] = ["#{div}#{line[1]}".strip]
42
end
43
44
# /interface ovpn-client
45
# these are the section headers
46
elsif %r{^(?<section>/[\w -]+)$} =~ line
47
header = section.strip
48
output[header] = [] # initialize
49
50
# take any line that isn't commented out
51
elsif !line.starts_with?('#') && !header.empty?
52
output[header] << line.strip
53
end
54
end
55
output
56
end
57
58
# this takes a string of config like 'add connect-to=10.99.99.99 name=l2tp-hm password=123 user=l2tp-hm'
59
# and converts it to a hash of keys and values for easier processing
60
def values_to_hash(line)
61
return {} unless line.is_a? String
62
63
hash = {}
64
array = line.split(' ')
65
array.each do |setting|
66
key_value = setting.split('=')
67
unless key_value.length == 2
68
next # skip things like 'add'
69
end
70
# verbose mode gives empty fields, however our processing makes them "" instead of empty
71
# so we check if its empty and skip it to prevent a double quote or escaped double quote
72
# field from being loaded
73
next if key_value[1].strip == '""' || key_value[1].strip == '\\"\\"'
74
75
hash[key_value[0].strip] = key_value[1].strip
76
end
77
hash
78
end
79
80
def mikrotik_swos_config_eater(thost, tport, config)
81
if framework.db.active
82
credential_data = {
83
address: thost,
84
port: tport,
85
protocol: 'tcp',
86
workspace_id: myworkspace_id,
87
origin_type: :service,
88
private_type: :password,
89
service_name: '',
90
module_fullname: fullname,
91
status: Metasploit::Model::Login::Status::UNTRIED
92
}
93
end
94
95
# Default SNMP to UDP
96
if tport == 161
97
credential_data[:protocol] = 'udp'
98
end
99
100
store_loot('mikrotik.config', 'text/plain', thost, config.strip, 'config.txt', 'MikroTik Configuration')
101
102
host_info = {
103
host: thost,
104
os_name: 'SwOS'
105
}
106
report_host(host_info)
107
108
# Unfortunately SWoS doesn't have a very easily parsable format. Config is exported in one big string
109
# they follow a key:value format followed by a comma, but since values can be arrays,
110
# or hashes the actual parsing of such would be pretty difficult. Since there isn't a ton of value
111
# in this file, we simply run some regexes against it
112
113
# ,sys.b:{id:'4d696b726f54696b2d637373333236',wdt:0x01,dsc:0x01,ivl:0x00,alla:0x00,allm:0x00,allp:0x03ffffff,avln:0x00,prio:0x8000,cost:0x00,igmp:0x00,ip:0x0158a8c0,iptp:0x02,dtrp:0x03ffffff,ainf:0x01}
114
# part we care about: ,ip:0x0158a8c0
115
if /,ip:0x(?<ip>[\da-f]+)/ =~ config
116
ip = ip.scan(/../).map { |x| x.hex.to_i }.reverse.join('.')
117
print_status("#{thost}:#{tport} IP Address: #{ip}")
118
end
119
120
# ,sys.b:{id:'4d696b726f54696b2d637373333236'
121
if /,sys.b:{id:'(?<host>[a-f\d]*)'/ =~ config
122
host = Array(host).pack('H*')
123
host_info[:name] = host
124
report_host(host_info)
125
print_good("#{thost}:#{tport} Hostname: #{host}")
126
end
127
128
# pull the switch password .pwd.b:{pwd:'61646d696e'} -> admin
129
# pull the switch password .pwd.b:{pwd:''} -> (blank, which is default)
130
if /,\.pwd\.b:{pwd:'(?<password>[a-f\d]*)'}/ =~ config
131
password = Array(password).pack('H*')
132
print_good("#{thost}:#{tport} Admin login password: #{password}")
133
if framework.db.active
134
cred = credential_data.dup
135
cred[:port] = 80
136
cred[:protocol] = 'tcp'
137
cred[:service_name] = 'www'
138
cred[:username] = 'admin' # hardcoded
139
cred[:private_data] = password.to_s
140
create_credential_and_login(cred)
141
end
142
end
143
144
# ,snmp.b:{en:0x01,com:'7075626c6963',ci:'636f6e74616374696e666f',loc:'6c6f636174696f6e'}
145
# ,snmp.b:{en:0x01,com:'7075626c6963',ci:'',loc:''}
146
if /,snmp\.b:{en:0x01,com:'(?<community>[a-f\d]*)',ci:'(?<contact>[a-f\d]*)',loc:'(?<location>[a-f\d]*)'}/ =~ config
147
community = Array(community).pack('H*')
148
contact = Array(contact).pack('H*')
149
location = Array(location).pack('H*')
150
print_good("#{thost}:#{tport} SNMP Community: #{community}, contact: #{contact}, location: #{location}")
151
if framework.db.active
152
cred = credential_data.dup
153
cred[:port] = 161
154
cred[:protocol] = 'udp'
155
cred[:service_name] = 'snmp'
156
cred[:private_data] = community.to_s
157
create_credential_and_login(cred)
158
end
159
end
160
161
# ,nm:['506f727431','506f727432','506f727433','506f727434','506f727435','506f727436','506f727437','506f727438','506f727439','506f72743130','506f72743131','506f72743132','506f72743133','506f72743134','506f72743135','506f72743136','506f72743137','506f72743138','506f72743139','506f72743230','506f72743231','506f72743232','506f72743233','75706c696e6b','53465031','53465032']}
162
if /,nm:\[(?<interfaces>[a-f\d',]*)\]/ =~ config
163
interfaces = interfaces.split(',')
164
interfaces.each_with_index do |iface, i|
165
iface = iface.gsub("'", '')
166
iface = Array(iface).pack('H*')
167
next if iface == "Port#{i + 1}" || /SFP\d/ =~ iface # skip defaults
168
169
print_status("#{thost}:#{tport} Port #{i + 1} Named: #{iface}")
170
end
171
end
172
173
end
174
175
def mikrotik_routeros_config_eater(thost, tport, config)
176
if framework.db.active
177
credential_data = {
178
address: thost,
179
port: tport,
180
protocol: 'tcp',
181
workspace_id: myworkspace_id,
182
origin_type: :service,
183
private_type: :password,
184
service_name: '',
185
module_fullname: fullname,
186
status: Metasploit::Model::Login::Status::UNTRIED
187
}
188
end
189
190
# Default SNMP to UDP
191
if tport == 161
192
credential_data[:protocol] = 'udp'
193
end
194
195
store_loot('mikrotik.config', 'text/plain', thost, config.strip, 'config.txt', 'MikroTik Configuration')
196
197
host_info = {
198
host: thost,
199
os_name: 'Mikrotik'
200
}
201
report_host(host_info)
202
203
if config.is_a? String
204
config = export_to_hash(config)
205
end
206
config.each do |header, values|
207
case header
208
#
209
# Cover OS details
210
#
211
when 'OS'
212
values.each do |value|
213
print_good("#{thost}:#{tport} OS: #{value}")
214
v = value.split(' ')
215
host_info[:os_name] = v[0]
216
host_info[:os_flavor] = v[1]
217
report_host(host_info)
218
end
219
220
#
221
# OpenVPN client details
222
#
223
when '/interface ovpn-client'
224
# https://wiki.mikrotik.com/wiki/Manual:Interface/OVPN#Client_Config
225
# add connect-to=10.99.99.98 mac-address=FE:45:B0:31:4A:34 name=ovpn-out1 password=password user=user
226
# add connect-to=10.99.99.98 disabled=yes mac-address=FE:45:B0:31:4A:34 name=ovpn-out3 password=password user=user
227
# no such thing as disabled=no, the value is just not there
228
values.each do |value|
229
next unless value.starts_with?('add ')
230
231
value = values_to_hash(value)
232
print_good("#{thost}:#{tport} #{value['disabled'] ? 'disabled' : ''} Open VPN Client to #{value['connect-to']} on mac #{value['mac-address']} named #{value['name']} with username #{value['user']} and password #{value['password']}")
233
next unless framework.db.active
234
235
cred = credential_data.dup
236
cred[:port] = 1194
237
cred[:service_name] = 'openvpn'
238
cred[:username] = value['user']
239
cred[:private_data] = value['password']
240
create_credential_and_login(cred)
241
end
242
243
#
244
# PPPoE client details
245
#
246
when '/interface pppoe-client'
247
# https://wiki.mikrotik.com/wiki/Manual:Interface/PPPoE#PPPoE_Client
248
# add disabled=no interface=ether2 name=pppoe-user password=password service-name=internet user=user
249
values.each do |value|
250
next unless value.starts_with?('add ')
251
252
value = values_to_hash(value)
253
print_good("#{thost}:#{tport} #{value['disabled'] ? '' : 'disabled'} PPPoE Client on #{value['interface']} named #{value['name']} and service name #{value['service-name']} with username #{value['user']} and password #{value['password']}")
254
next unless framework.db.active
255
256
cred = credential_data.dup
257
cred[:username] = value['user']
258
cred[:service_name] = 'pppoe'
259
cred[:private_data] = value['password']
260
create_credential_and_login(cred)
261
end
262
263
#
264
# L2TP client details
265
#
266
when '/interface l2tp-client'
267
# https://wiki.mikrotik.com/wiki/Manual:Interface/L2TP#L2TP_Client
268
# add connect-to=10.99.99.99 name=l2tp-hm password=123 user=l2tp-hm
269
values.each do |value|
270
next unless value.starts_with?('add ')
271
272
value = values_to_hash(value)
273
print_good("#{thost}:#{tport} #{value['disabled'] ? '' : 'disabled'} L2TP Client to #{value['connect-to']} named #{value['name']} with username #{value['user']} and password #{value['password']}")
274
next unless framework.db.active
275
276
cred = credential_data.dup
277
cred[:port] = 1701
278
cred[:service_name] = 'l2tp'
279
cred[:username] = value['user']
280
cred[:private_data] = value['password']
281
create_credential_and_login(cred)
282
end
283
#
284
# PPTP client details
285
#
286
when '/interface pptp-client'
287
# https://wiki.mikrotik.com/wiki/Manual:Interface/PPTP#PPTP_Client
288
# add connect-to=10.99.99.99 disabled=no name=pptp-hm password=123 user=pptp-hm
289
values.each do |value|
290
next unless value.starts_with?('add ')
291
292
value = values_to_hash(value)
293
print_good("#{thost}:#{tport} #{value['disabled'] ? '' : 'disabled'} PPTP Client to #{value['connect-to']} named #{value['name']} with username #{value['user']} and password #{value['password']}")
294
next unless framework.db.active
295
296
cred = credential_data.dup
297
cred[:service_name] = 'pptp'
298
cred[:port] = 1723
299
cred[:username] = value['user']
300
cred[:private_data] = value['password']
301
create_credential_and_login(cred)
302
end
303
#
304
# SNMP details
305
#
306
when '/snmp community'
307
# https://wiki.mikrotik.com/wiki/Manual:SNMP
308
# add addresses=::/0 authentication-password=write name=write write-access=yes
309
values.each do |value|
310
next unless value.starts_with?('add ')
311
312
value = values_to_hash(value)
313
if value['encryption-password'] # v3
314
print_good("#{thost}:#{tport} SNMP community #{value['name']} with password #{value['authentication-password']}(#{value['authentication-protocol']}), encryption password #{value['encryption-password']}(#{value['encryption-protocol']}) and #{value['write-access'] ? 'write access' : 'read only'}")
315
else
316
print_good("#{thost}:#{tport} SNMP community #{value['name']} with password #{value['authentication-password']} and #{value['write-access'] ? 'write access' : 'read only'}")
317
end
318
319
next unless framework.db.active
320
321
cred = credential_data.dup
322
if value['write-access'] == 'yes'
323
cred[:access_level] = 'RW'
324
else
325
cred[:access_level] = 'RO'
326
end
327
cred[:protocol] = 'udp'
328
cred[:port] = 161
329
cred[:service_name] = 'snmp'
330
cred[:private_data] = value['name']
331
create_credential_and_login(cred)
332
end
333
#
334
# PPP tunnel bridging secret details
335
#
336
when '/ppp secret'
337
# https://wiki.mikrotik.com/wiki/Manual:BCP_bridging_(PPP_tunnel_bridging)#Office_1_configuration
338
# add name=ppp1 password=password profile=ppp_bridge
339
values.each do |value|
340
next unless value.starts_with?('add ')
341
342
value = values_to_hash(value)
343
print_good("#{thost}:#{tport} #{value['disabled'] ? 'disabled' : ''} PPP tunnel bridging named #{value['name']} with profile name #{value['profile']} and password #{value['password']}")
344
next unless framework.db.active
345
346
cred = credential_data.dup
347
cred[:username] = ''
348
cred[:private_data] = value['password']
349
create_credential_and_login(cred)
350
end
351
#
352
# SMB users details
353
#
354
when '/ip smb users'
355
# https://wiki.mikrotik.com/wiki/Manual:IP/SMB#User_setup
356
# add name=mtuser password=mtpasswd read-only=no
357
# add disabled=yes name=disableduser password=disabledpasswd
358
values.each do |value|
359
next unless value.starts_with?('add ')
360
361
value = values_to_hash(value)
362
print_good("#{thost}:#{tport} #{value['disabled'] == 'yes' ? 'disabled' : ''} SMB Username #{value['name']} and password #{value['password']}#{' with RO only access' if value['read-only'] == 'yes' || !value['read-only']}")
363
next unless framework.db.active
364
365
cred = credential_data.dup
366
if value['read-only'] == 'yes' || !value['read-only']
367
cred[:access_level] = 'RO'
368
end
369
cred[:service_name] = 'smb'
370
cred[:username] = value['name']
371
cred[:private_data] = value['password']
372
create_credential_and_login(cred)
373
end
374
#
375
# SMTP user details
376
#
377
when '/tool e-mail'
378
# https://wiki.mikrotik.com/wiki/Manual:Tools/email#Properties
379
# set address=1.1.1.1 [email protected] password=smtppassword user=smtpuser
380
values.each do |value|
381
next unless value.starts_with?('set ')
382
383
value = values_to_hash(value)
384
print_good("#{thost}:#{tport} SMTP Username #{value['user']} and password #{value['password']} for #{value['address']}:#{value['port'] || '25'}")
385
next unless framework.db.active
386
387
cred = credential_data.dup
388
cred[:service_name] = 'smtp'
389
cred[:port] = value['port'] ? value['port'].to_i : 25
390
cred[:address] = value['address']
391
cred[:protocol] = 'tcp'
392
cred[:username] = value['user']
393
cred[:private_data] = value['password']
394
create_credential_and_login(cred)
395
end
396
#
397
# Wireless networks details
398
#
399
when '/interface wireless security-profiles'
400
# https://wiki.mikrotik.com/wiki/Manual:Interface/Wireless#Security_Profiles
401
# add name=openwifi supplicant-identity=MikroTik
402
# add authentication-types=wpa-psk mode=dynamic-keys name=wpawifi supplicant-identity=MikroTik wpa-pre-shared-key=presharedkey
403
# add authentication-types=wpa2-psk mode=dynamic-keys name=wpa2wifi supplicant-identity=MikroTik wpa2-pre-shared-key=presharedkey
404
# add authentication-types=wpa2-eap mode=dynamic-keys mschapv2-password=password mschapv2-username=username name=wpaeapwifi supplicant-identity=MikroTik
405
# add mode=static-keys-required name=wepwifi static-key-0=0123456789 static-key-1=0987654321 static-key-2=1234509876 static-key-3=0192837645 supplicant-identity=MikroTik
406
values.each do |value|
407
next unless value.starts_with?('add ')
408
409
value = values_to_hash(value)
410
output = "#{thost}:#{tport} Wireless AP #{value['name']}"
411
412
if !value['authentication-types'] && (!value['mode'] || value['mode'] == 'none') # open wifi
413
output << ' with no encryption (open wifi)'
414
vprint_good(output)
415
next
416
end
417
418
if framework.db.active
419
cred = credential_data.dup
420
else
421
cred = {}
422
end
423
424
# The following section is a little complicated due to the way mikrotik exports values
425
# in compact/terse/default mode, it will skip printing default values, so we have to check
426
# that keys are present.
427
# In verbose mode, they will print but be empty
428
if value['wpa-pre-shared-key'] && !value['wpa-pre-shared-key'].empty?
429
output << " with WPA password #{value['wpa-pre-shared-key']}"
430
if framework.db.active
431
cred[:private_data] = value['wpa-pre-shared-key']
432
create_credential_and_login(cred)
433
end
434
elsif value['wpa2-pre-shared-key'] && !value['wpa2-pre-shared-key'].empty?
435
output << " with WPA2 password #{value['wpa2-pre-shared-key']}"
436
if framework.db.active
437
cred[:private_data] = value['wpa2-pre-shared-key']
438
create_credential_and_login(cred)
439
end
440
elsif value['authentication-types'] == 'wpa2-eap'
441
output << " with WPA2-EAP username #{value['mschapv2-username']} password #{value['mschapv2-password']}"
442
if framework.db.active
443
cred[:username] = value['mschapv2-username']
444
cred[:private_data] = value['mschapv2-password']
445
create_credential_and_login(cred)
446
end
447
elsif value['static-key-0'] || value['static-key-1'] || value['static-key-2'] || value['static-key-3']
448
(0..3).each do |i|
449
key = "static-key-#{i}"
450
next unless value[key]
451
452
output << " with WEP password #{value[key]}"
453
if framework.db.active
454
cred[:private_data] = value[key]
455
create_credential_and_login(cred) # run for each key we find
456
end
457
end
458
end
459
460
print_good(output)
461
end
462
463
#
464
# hostname details
465
#
466
when '/system identity'
467
# https://wiki.mikrotik.com/wiki/Manual:System/identity#Configuration
468
# set name=mikrotik_hostname
469
values.each do |value|
470
next unless value.starts_with?('set ')
471
472
value = values_to_hash(value)
473
host_info[:name] = value['name']
474
report_host(host_info)
475
end
476
end
477
end
478
end
479
end
480
end
481
482