2013-06-13 15:08:44 +00:00
|
|
|
##
|
|
|
|
# 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/
|
|
|
|
##
|
|
|
|
|
2013-05-29 22:36:53 +00:00
|
|
|
require 'msf/core'
|
|
|
|
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
|
|
|
|
include Msf::Exploit::Remote::Tcp
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
include Msf::Auxiliary::Scanner
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
super(
|
2013-06-13 15:08:44 +00:00
|
|
|
'Name' => 'SAPRouter Port Scanner',
|
|
|
|
'Description' => %q{
|
|
|
|
This module allows for mapping ACLs and identify open/closed ports accessible
|
|
|
|
on hosts through a saprouter.
|
|
|
|
},
|
|
|
|
'Author' => [
|
|
|
|
'Bruno Morisson <bm[at]integrity.pt>', # metasploit module
|
|
|
|
'nmonkee' # saprouter packet building code from sapcat.rb
|
|
|
|
],
|
|
|
|
'References' =>
|
|
|
|
[
|
|
|
|
# General
|
|
|
|
['URL', 'http://help.sap.com/saphelp_nw70/helpdata/EN/4f/992dfe446d11d189700000e8322d00/frameset.htm'],
|
|
|
|
['URL', 'http://help.sap.com/saphelp_dimp50/helpdata/En/f8/bb960899d743378ccb8372215bb767/content.htm'],
|
|
|
|
['URL', 'http://labs.mwrinfosecurity.com/blog/2012/09/13/sap-smashing-internet-windows/'],
|
|
|
|
['URL', 'http://conference.hitb.org/hitbsecconf2010ams/materials/D2T2%20-%20Mariano%20Nunez%20Di%20Croce%20-%20SAProuter%20.pdf'],
|
|
|
|
['URL', 'http://scn.sap.com/docs/DOC-17124'] # SAP default ports
|
|
|
|
],
|
|
|
|
'License' => MSF_LICENSE
|
2013-05-29 22:36:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
register_options(
|
2013-06-13 15:08:44 +00:00
|
|
|
[
|
|
|
|
OptAddress.new('SAPROUTER_HOST', [true, 'SAPRouter address', '']),
|
|
|
|
OptPort.new('SAPROUTER_PORT', [true, 'SAPRouter TCP port', '3299']),
|
|
|
|
OptEnum.new('MODE', [true, 'Connection Mode: 0 for NI_MSG_IO (SAP), 1 for NI_RAW_IO (TCP), 2 for NI_ROUT_IO (ROUTER) ', 0, [0, 1, 2]]),
|
|
|
|
OptString.new('PORTS', [true, 'Ports to scan (e.g. 22-25,80,110-900)', '3200-3299']),
|
|
|
|
OptInt.new('TIMEOUT', [true, 'The socket connect timeout in milliseconds', 1000]),
|
|
|
|
OptInt.new('CONCURRENCY', [true, 'The number of concurrent ports to check per host', 10]),
|
|
|
|
], self.class)
|
2013-05-29 22:36:53 +00:00
|
|
|
|
|
|
|
deregister_options('RPORT')
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_ni_packet(routes)
|
|
|
|
|
|
|
|
mode = datastore['MODE'].to_i
|
|
|
|
route_data=''
|
2013-06-13 15:08:44 +00:00
|
|
|
ni_packet = [
|
|
|
|
'NI_ROUTE',
|
|
|
|
0,
|
|
|
|
2,
|
|
|
|
39,
|
|
|
|
2,
|
|
|
|
mode,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
1
|
|
|
|
].pack("A8c8")
|
2013-05-29 22:36:53 +00:00
|
|
|
|
|
|
|
first = false
|
|
|
|
|
|
|
|
routes.each do |host, port| # create routes
|
2013-06-13 15:08:44 +00:00
|
|
|
route_item = [host, 0, port.to_s, 0, 0].pack("A*CA*cc")
|
2013-05-29 22:36:53 +00:00
|
|
|
if !first
|
2013-06-13 15:08:44 +00:00
|
|
|
route_data = [route_data, route_item.length, route_item].pack("A*NA*")
|
2013-05-29 22:36:53 +00:00
|
|
|
first = true
|
|
|
|
else
|
|
|
|
route_data = route_data << route_item
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ni_packet << [route_data.length - 4].pack('N') << route_data # add routes to packet
|
|
|
|
ni_packet = [ni_packet.length].pack('N') << ni_packet # add size
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_response_packet(response, ip, port)
|
|
|
|
|
2013-06-13 15:08:44 +00:00
|
|
|
#vprint_error("#{ip}:#{port} - response packet: #{response}")
|
2013-05-29 22:36:53 +00:00
|
|
|
|
|
|
|
case response
|
2013-05-31 23:31:06 +00:00
|
|
|
when /NI_RTERR/
|
|
|
|
case response
|
|
|
|
when /timed out/
|
2013-06-13 15:08:44 +00:00
|
|
|
vprint_error ("#{ip}:#{port} - connection timed out")
|
2013-05-31 23:31:06 +00:00
|
|
|
when /refused/
|
2013-06-13 15:08:44 +00:00
|
|
|
vprint_error("#{ip}:#{port} - TCP closed")
|
|
|
|
return [ip, port, "closed"]
|
2013-05-31 23:31:06 +00:00
|
|
|
when /denied/
|
2013-06-13 15:08:44 +00:00
|
|
|
vprint_error("#{ip}:#{port} - blocked by ACL")
|
2013-05-31 23:31:06 +00:00
|
|
|
when /invalid/
|
2013-06-13 15:08:44 +00:00
|
|
|
vprint_error("#{ip}:#{port} - invalid route")
|
2013-05-31 23:31:06 +00:00
|
|
|
when /reacheable/
|
2013-06-13 15:08:44 +00:00
|
|
|
vprint_error("#{ip}:#{port} - unreachable")
|
2013-05-29 22:36:53 +00:00
|
|
|
else
|
2013-06-13 15:08:44 +00:00
|
|
|
vprint_error("#{ip}:#{port} - unknown error message")
|
2013-05-31 23:31:06 +00:00
|
|
|
end
|
|
|
|
when /NI_PONG/
|
|
|
|
print_good("#{ip}:#{port} - TCP OPEN")
|
2013-06-13 15:08:44 +00:00
|
|
|
return [ip, port, "open"]
|
2013-05-31 23:31:06 +00:00
|
|
|
else
|
2013-06-13 15:08:44 +00:00
|
|
|
vprint_error("#{ip}:#{port} - unknown response")
|
2013-05-29 22:36:53 +00:00
|
|
|
end
|
|
|
|
|
2013-06-13 15:08:44 +00:00
|
|
|
return nil
|
2013-05-29 22:36:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def run_host(ip)
|
|
|
|
|
|
|
|
timeout = datastore['TIMEOUT'].to_i
|
|
|
|
ports = Rex::Socket.portspec_crack(datastore['PORTS'])
|
|
|
|
|
|
|
|
sap_host = datastore['SAPROUTER_HOST']
|
|
|
|
sap_port = datastore['SAPROUTER_PORT']
|
|
|
|
|
|
|
|
if ports.empty?
|
|
|
|
print_error('Error: No valid ports specified')
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
print_status("Scanning #{ip}")
|
2013-05-31 23:31:06 +00:00
|
|
|
thread = []
|
2013-06-13 15:08:44 +00:00
|
|
|
r = []
|
2013-05-29 22:36:53 +00:00
|
|
|
|
2013-06-13 15:08:44 +00:00
|
|
|
begin
|
|
|
|
ports.each do |port|
|
2013-05-29 22:36:53 +00:00
|
|
|
|
2013-05-31 23:31:06 +00:00
|
|
|
if thread.length >= datastore['CONCURRENCY']
|
|
|
|
# Assume the first thread will be among the earliest to finish
|
|
|
|
thread.first.join
|
|
|
|
end
|
|
|
|
thread << framework.threads.spawn("Module(#{self.refname})-#{ip}:#{port}", false) do
|
|
|
|
|
|
|
|
begin
|
2013-06-13 15:08:44 +00:00
|
|
|
# create ni_packet to send to saprouter
|
|
|
|
routes = {sap_host => sap_port, ip => port}
|
|
|
|
ni_packet = build_ni_packet(routes)
|
|
|
|
|
2013-05-31 23:31:06 +00:00
|
|
|
s = connect(false,
|
|
|
|
{
|
|
|
|
'RPORT' => sap_port,
|
|
|
|
'RHOST' => sap_host,
|
|
|
|
'ConnectTimeout' => (timeout / 1000.0)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
s.write(ni_packet, ni_packet.length)
|
|
|
|
response = s.get()
|
|
|
|
|
2013-06-13 15:08:44 +00:00
|
|
|
res = parse_response_packet(response, ip, port)
|
|
|
|
if res
|
|
|
|
r << res
|
|
|
|
end
|
2013-05-31 23:31:06 +00:00
|
|
|
|
|
|
|
rescue ::Rex::ConnectionRefused
|
|
|
|
print_error("#{ip}:#{port} - Unable to connect to SAPRouter #{sap_host}:#{sap_port} - Connection Refused")
|
|
|
|
|
|
|
|
rescue ::Rex::ConnectionError, ::IOError, ::Timeout::Error
|
|
|
|
rescue ::Rex::Post::Meterpreter::RequestError
|
|
|
|
rescue ::Interrupt
|
|
|
|
raise $!
|
|
|
|
ensure
|
|
|
|
disconnect(s) rescue nil
|
|
|
|
end
|
2013-05-29 22:36:53 +00:00
|
|
|
end
|
|
|
|
end
|
2013-05-31 23:31:06 +00:00
|
|
|
thread.each { |x| x.join }
|
|
|
|
|
2013-06-13 15:08:44 +00:00
|
|
|
rescue ::Timeout::Error
|
|
|
|
ensure
|
|
|
|
thread.each { |x| x.kill rescue nil }
|
|
|
|
end
|
|
|
|
|
|
|
|
r.each do |res|
|
|
|
|
report_service(:host => res[0], :port => res[1], :state => res[2])
|
|
|
|
end
|
|
|
|
|
2013-05-29 22:36:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|