metasploit-framework/modules/auxiliary/scanner/sap/sap_router_portscanner.rb

407 lines
13 KiB
Ruby

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Report
VALID_HOSTNAME_REGEX = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/
def initialize
super(
'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 and default sap ports information
],
'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
)
register_options(
[
OptAddress.new('RHOST', [true, 'SAPRouter address', '']),
OptPort.new('RPORT', [true, 'SAPRouter TCP port', '3299']),
OptString.new('TARGETS', [true, 'Comma delimited targets. When resolution is local address ranges or CIDR identifiers allowed.', '']),
OptEnum.new('MODE', [true, 'Connection Mode: SAP_PROTO or TCP ', 'SAP_PROTO', ['SAP_PROTO', 'TCP']]),
OptString.new('INSTANCES', [false, 'SAP instance numbers to scan (NN in PORTS definition)', '00-99']),
OptString.new('PORTS', [true, 'Ports to scan (e.g. 3200-3299,5NN13)', '32NN']),
# Default ports: 32NN,33NN,48NN,80NN,36NN,81NN,5NN00-5NN19,21212,21213,
# 59975,59976,4238-4241,3299,3298,515,7200,7210,7269,7270,7575,39NN,
# 3909,4NN00,8200,8210,8220,8230,4363,4444,4445,9999,3NN01-3NN08,
# 3NN11,3NN17,20003-20007,31596,31597,31602,31601,31604,2000-2002,
# 8355,8357,8351-8353,8366,1090,1095,20201,1099,1089,443NN,444NN
OptInt.new('CONCURRENCY', [true, 'The number of concurrent ports to check per host', 10]),
OptEnum.new('RESOLVE',[true,'Where to resolve TARGETS','local',['remote','local']])
], self.class)
end
# Converts a instance specification like "4,21-23,33" into a sorted,
# unique array of valid port numbers like [4,21,22,23,33]
def sap_instance_to_list(instance)
instances = []
return if !instance
# Build ports array from port specification
instance.split(/,/).each do |item|
start, stop = item.split(/-/).map { |p| p.to_i }
start ||= 0
stop ||= item.match(/-/) ? 99 : start
start, stop = stop, start if stop < start
start.upto(stop) { |p| instances << p }
end
# Sort, and remove dups and invalid instances
instances.sort.uniq.delete_if { |p| p < 0 or p > 99 }
end
def build_sap_ports(ports)
sap_ports = []
sap_instances = sap_instance_to_list(datastore['INSTANCES'])
# if we have INSTANCES, let's fill in the NN on PORTS
if sap_instances and ports.include? 'NN'
sap_instances.each { |i| sap_ports << (ports.gsub('NN', "%02d" % i)).to_s }
ports = Rex::Socket.portspec_crack(sap_ports.join(','))
else
ports = Rex::Socket.portspec_crack(ports)
end
return ports
end
def build_ni_packet(routes)
mode = {'SAP_PROTO' => 0, 'TCP' => 1}[datastore['MODE']]
route_data=''
ni_packet = [
'NI_ROUTE',
0,
2,
39,
2,
mode,
0,
0,
1
].pack("A8c8")
first = false
routes.each do |host, port| # create routes
route_item = [host, 0, port.to_s, 0, 0].pack("A*CA*cc")
if !first
route_data = [route_data, route_item.length, route_item].pack("A*NA*")
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 sap_port_info(port)
case port.to_s
when /^3299$/
service = "SAP Router"
when /^3298$/
service = "SAP niping (Network Test Program)"
when /^32[0-9][0-9]/
service = "SAP Dispatcher sapdp" + port.to_s[-2, 2]
when /^33[0-9][0-9]/
service = "SAP Gateway sapgw" + port.to_s[-2, 2]
when /^48[0-9][0-9]/
service = "SAP Gateway [SNC] sapgw" + port.to_s[-2, 2]
when /^80[0-9][0-9]/
service = "SAP ICM HTTP"
when /^443[0-9][0-9]/
service = "SAP ICM HTTPS"
when /^36[0-9][0-9]/
service = "SAP Message Server sapms<SID>" + port.to_s[-2, 2]
when /^81[0-9][0-9]/
service = "SAP Message Server [HTTP]"
when /^444[0-9][0-9]/
service = "SAP Message Server [HTTPS]"
when /^5[0-9][0-9]00/
service = "SAP JAVA EE Dispatcher [HTTP]"
when /^5[0-9][0-9]01/
service = "SAP JAVA EE Dispatcher [HTTPS]"
when /^5[0-9][0-9]02/
service = "SAP JAVA EE Dispatcher [IIOP]"
when /^5[0-9][0-9]03/
service = "SAP JAVA EE Dispatcher [IIOP over SSL]"
when /^5[0-9][0-9]04/
service = "SAP JAVA EE Dispatcher [P4]"
when /^5[0-9][0-9]05/
service = "SAP JAVA EE Dispatcher [P4 over HTTP]"
when /^5[0-9][0-9]06/
service = "SAP JAVA EE Dispatcher [P4 over SSL]"
when /^5[0-9][0-9]07/
service = "SAP JAVA EE Dispatcher [IIOP]"
when /^5[0-9][0-9]08$/
service = "SAP JAVA EE Dispatcher [Telnet]"
when /^5[0-9][0-9]10/
service = "SAP JAVA EE Dispatcher [JMS]"
when /^5[0-9][0-9]16/
service = "SAP JAVA Enq. Replication"
when /^5[0-9][0-9]13/
service = "SAP StartService [SOAP] sapctrl" + port.to_s[1, 2]
when /^5[0-9][0-9]14/
service = "SAP StartService [SOAP over SSL] sapctrl" + port.to_s[1, 2]
when /^5[0-9][0-9]1(7|8|9)/
service = "SAP Software Deployment Manager"
when /^2121(2|3)/
service = "SAPinst"
when /^5997(5|6)/
service = "SAPinst (IBM AS/400 iSeries)"
when /^42(3|4)(8|9|0|1$)/
service = "SAP Upgrade"
when /^515$/
service = "SAPlpd"
when /^7(2|5)(00|10|69|70|75$)/
service = "LiveCache MaxDB (formerly SAP DB)"
when /^5[0-9][0-9]15/
service = "DTR - Design Time Repository"
when /^3909$/
service = "ITS MM (Mapping Manager) sapvwmm00_<INST>"
when /^39[0-9][0-9]$/
service = "ITS AGate sapavw00_<INST>"
when /^4[0-9][0-9]00/
service = "IGS Multiplexer"
when /^8200$/
service = "XI JMS/JDBC/File Adapter"
when /^8210$/
service = "XI JMS Adapter"
when /^8220$/
service = "XI JDBC Adapter"
when /^8230$/
service = "XI File Adapter"
when /^4363$/
service = "IPC Dispatcher"
when /^4444$/
service = "IPC Dispatcher"
when /^4445$/
service = "IPC Data Loader"
when /^9999$/
service = "IPC Server"
when /^3[0-9][0-9](0|1)(1|2|3|4|5|6|7|8$)/
service = "SAP Software Deployment Manager"
when /^2000(3|4|5|6|7$)/
service = "MDM (Master Data Management)"
when /^3159(6|7$)/
service = "MDM (Master Data Management)"
when /^3160(2|3|4$)/
service = "MDM (Master Data Management)"
when /^200(0|1|2$)/
service = "MDM Server (Master Data Management)"
when /^83(5|6)(1|2|3|5|6|7$)/
service = "MDM Server (Master Data Management)"
when /^109(0|5$)/
service = "Content Server / Cache Server"
when /^20201$/
service = "CRM - Central Software Deployment Manager"
when /^10(8|9)9$/
service = "PAW - Performance Assessment Workbench"
else
service = ''
end
end
def parse_response_packet(response, ip, port)
#vprint_error("#{ip}:#{port} - response packet: #{response}")
case response
when /NI_RTERR/
case response
when /timed out/
vprint_error ("#{ip}:#{port} - connection timed out")
when /refused/
vprint_error("#{ip}:#{port} - TCP closed")
return [ip, port, "closed", sap_port_info(port)]
when /denied/
vprint_error("#{ip}:#{port} - blocked by ACL")
when /invalid/
vprint_error("#{ip}:#{port} - invalid route")
when /reacheable/
vprint_error("#{ip}:#{port} - unreachable")
when /hostname '#{ip}' unknown/
vprint_error("#{ip}:#{port} - unknown host")
when /GetHostByName: '#{ip}' not found/
vprint_error("#{ip}:#{port} - unknown host")
when /connection to .* timed out/
vprint_error("#{ip}:#{port} - connection timed out")
when /partner .* not reached/
vprint_error("#{ip}:#{port} - host unreachable")
else
vprint_error("#{ip}:#{port} - unknown error message")
end
when /NI_PONG/
vprint_good("#{ip}:#{port} - TCP OPEN")
return [ip, port, "open", sap_port_info(port)]
else
vprint_error("#{ip}:#{port} - unknown response")
end
return nil
end
def validate(range)
hosts_list = range.split(",")
return false if hosts_list.nil? or hosts_list.empty?
hosts_list.each do |host|
unless Rex::Socket.is_ipv6?(host) || Rex::Socket.is_ipv4?(host) || host =~ VALID_HOSTNAME_REGEX
return false
end
end
end
def run
if datastore['RESOLVE'] == 'remote'
range = datastore['TARGETS']
unless validate(range)
print_error("TARGETS must be a comma separated list of IP addresses or hostnames when RESOLVE is remote")
return
end
range.split(/,/).each do |host|
run_host(host)
end
else
# resolve IP or crack IP range
ip_list = Rex::Socket::RangeWalker.new(datastore['TARGETS'])
ip_list.each do |ip|
run_host(ip)
end
end
end
def run_host(ip)
ports = datastore['PORTS']
# if port definition has NN then we require INSTANCES
if ports.include? 'NN' and datastore['INSTANCES'].nil?
print_error('Error: No instances specified')
return
end
ports = build_sap_ports(ports)
if ports.empty?
raise Msf::OptionValidateError.new(['PORTS'])
end
print_status("Scanning #{ip}")
thread = []
r = []
begin
ports.each do |port|
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
# create ni_packet to send to saprouter
routes = {rhost => rport, ip => port}
ni_packet = build_ni_packet(routes)
s = connect(false)
s.write(ni_packet, ni_packet.length)
response = s.get()
res = parse_response_packet(response, ip, port)
if res
r << res
end
rescue ::Rex::ConnectionRefused
print_error("#{ip}:#{port} - Unable to connect to SAPRouter #{rhost}:#{rport} - Connection Refused")
rescue ::Rex::ConnectionError, ::IOError, ::Timeout::Error
rescue ::Rex::Post::Meterpreter::RequestError
rescue ::Interrupt
raise $!
ensure
disconnect(s) rescue nil
end
end
end
thread.each { |x| x.join }
rescue ::Timeout::Error
ensure
thread.each { |x| x.kill rescue nil }
end
tbl = Msf::Ui::Console::Table.new(
Msf::Ui::Console::Table::Style::Default,
'Header' => "Portscan Results",
'Prefix' => "\n",
'Postfix' => "\n",
'Indent' => 1,
'Columns' =>
[
"Host",
"Port",
"State",
"Info",
])
r.each do |res|
tbl << [res[0], res[1], res[2], res[3]]
# we can't report if resolution is remote, since host is unknown locally
if datastore['RESOLVE'] == 'local'
begin
report_service(:host => res[0], :port => res[1], :state => res[2])
rescue ActiveRecord::RecordInvalid
# Probably raised because the Address is reserved, for example
# when trying to report a service on 127.0.0.1
print_warning("Can't report #{res[0]} as host to the database")
end
end
end
print_warning("Warning: Service info could be inaccurate")
print(tbl.to_s)
end
end