## # 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 ', # 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" + 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_" when /^39[0-9][0-9]$/ service = "ITS AGate sapavw00_" 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