|
| 1 | +## |
| 2 | +# This file is part of the Metasploit Framework and may be subject to |
| 3 | +# redistribution and commercial restrictions. Please see the Metasploit |
| 4 | +# web site for more information on licensing and terms of use. |
| 5 | +# http://metasploit.com/ |
| 6 | +## |
| 7 | + |
| 8 | +require 'msf/core' |
| 9 | + |
| 10 | +class Metasploit3 < Msf::Auxiliary |
| 11 | + |
| 12 | + include Msf::Exploit::Remote::Tcp |
| 13 | + include Msf::Auxiliary::Report |
| 14 | + include Msf::Auxiliary::Scanner |
| 15 | + |
| 16 | + def initialize |
| 17 | + super( |
| 18 | + 'Name' => 'SAPRouter Port Scanner', |
| 19 | + 'Description' => %q{ |
| 20 | + This module allows for mapping ACLs and identify open/closed ports accessible |
| 21 | + on hosts through a saprouter. |
| 22 | + }, |
| 23 | + 'Author' => [ |
| 24 | + 'Bruno Morisson <bm[at]integrity.pt>', # metasploit module |
| 25 | + 'nmonkee' # saprouter packet building code from sapcat.rb |
| 26 | + ], |
| 27 | + 'References' => |
| 28 | + [ |
| 29 | + # General |
| 30 | + ['URL', 'http://help.sap.com/saphelp_nw70/helpdata/EN/4f/992dfe446d11d189700000e8322d00/frameset.htm'], |
| 31 | + ['URL', 'http://help.sap.com/saphelp_dimp50/helpdata/En/f8/bb960899d743378ccb8372215bb767/content.htm'], |
| 32 | + ['URL', 'http://labs.mwrinfosecurity.com/blog/2012/09/13/sap-smashing-internet-windows/'], |
| 33 | + ['URL', 'http://conference.hitb.org/hitbsecconf2010ams/materials/D2T2%20-%20Mariano%20Nunez%20Di%20Croce%20-%20SAProuter%20.pdf'], |
| 34 | + ['URL', 'http://scn.sap.com/docs/DOC-17124'] # SAP default ports |
| 35 | + ], |
| 36 | + 'License' => MSF_LICENSE |
| 37 | + ) |
| 38 | + |
| 39 | + register_options( |
| 40 | + [ |
| 41 | + OptAddress.new('SAPROUTER_HOST', [true, 'SAPRouter address', '']), |
| 42 | + OptPort.new('SAPROUTER_PORT', [true, 'SAPRouter TCP port', '3299']), |
| 43 | + OptEnum.new('MODE', [true, 'Connection Mode: SAP_PROTO or TCP ', 'SAP_PROTO', ['SAP_PROTO', 'TCP']]), |
| 44 | + OptString.new('PORTS', [true, 'Ports to scan (e.g. 22-25,80,110-900)', '3200-3299']), |
| 45 | + OptInt.new('CONCURRENCY', [true, 'The number of concurrent ports to check per host', 10]), |
| 46 | + ], self.class) |
| 47 | + |
| 48 | + deregister_options('RPORT') |
| 49 | + |
| 50 | + end |
| 51 | + |
| 52 | + def build_ni_packet(routes) |
| 53 | + |
| 54 | + mode = {'SAP_PROTO'=>0,'TCP'=>1}[datastore['MODE']] |
| 55 | + |
| 56 | + route_data='' |
| 57 | + ni_packet = [ |
| 58 | + 'NI_ROUTE', |
| 59 | + 0, |
| 60 | + 2, |
| 61 | + 39, |
| 62 | + 2, |
| 63 | + mode, |
| 64 | + 0, |
| 65 | + 0, |
| 66 | + 1 |
| 67 | + ].pack("A8c8") |
| 68 | + |
| 69 | + first = false |
| 70 | + |
| 71 | + routes.each do |host, port| # create routes |
| 72 | + route_item = [host, 0, port.to_s, 0, 0].pack("A*CA*cc") |
| 73 | + if !first |
| 74 | + route_data = [route_data, route_item.length, route_item].pack("A*NA*") |
| 75 | + first = true |
| 76 | + else |
| 77 | + route_data = route_data << route_item |
| 78 | + end |
| 79 | + end |
| 80 | + |
| 81 | + ni_packet << [route_data.length - 4].pack('N') << route_data # add routes to packet |
| 82 | + ni_packet = [ni_packet.length].pack('N') << ni_packet # add size |
| 83 | + end |
| 84 | + |
| 85 | + def parse_response_packet(response, ip, port) |
| 86 | + |
| 87 | + #vprint_error("#{ip}:#{port} - response packet: #{response}") |
| 88 | + |
| 89 | + case response |
| 90 | + when /NI_RTERR/ |
| 91 | + case response |
| 92 | + when /timed out/ |
| 93 | + vprint_error ("#{ip}:#{port} - connection timed out") |
| 94 | + when /refused/ |
| 95 | + vprint_error("#{ip}:#{port} - TCP closed") |
| 96 | + return [ip, port, "closed"] |
| 97 | + when /denied/ |
| 98 | + vprint_error("#{ip}:#{port} - blocked by ACL") |
| 99 | + when /invalid/ |
| 100 | + vprint_error("#{ip}:#{port} - invalid route") |
| 101 | + when /reacheable/ |
| 102 | + vprint_error("#{ip}:#{port} - unreachable") |
| 103 | + else |
| 104 | + vprint_error("#{ip}:#{port} - unknown error message") |
| 105 | + end |
| 106 | + when /NI_PONG/ |
| 107 | + print_good("#{ip}:#{port} - TCP OPEN") |
| 108 | + return [ip, port, "open"] |
| 109 | + else |
| 110 | + vprint_error("#{ip}:#{port} - unknown response") |
| 111 | + end |
| 112 | + |
| 113 | + return nil |
| 114 | + end |
| 115 | + |
| 116 | + def run_host(ip) |
| 117 | + |
| 118 | + ports = Rex::Socket.portspec_crack(datastore['PORTS']) |
| 119 | + |
| 120 | + sap_host = datastore['SAPROUTER_HOST'] |
| 121 | + sap_port = datastore['SAPROUTER_PORT'] |
| 122 | + |
| 123 | + if ports.empty? |
| 124 | + print_error('Error: No valid ports specified') |
| 125 | + return |
| 126 | + end |
| 127 | + |
| 128 | + print_status("Scanning #{ip}") |
| 129 | + thread = [] |
| 130 | + r = [] |
| 131 | + |
| 132 | + begin |
| 133 | + ports.each do |port| |
| 134 | + |
| 135 | + if thread.length >= datastore['CONCURRENCY'] |
| 136 | + # Assume the first thread will be among the earliest to finish |
| 137 | + thread.first.join |
| 138 | + end |
| 139 | + thread << framework.threads.spawn("Module(#{self.refname})-#{ip}:#{port}", false) do |
| 140 | + |
| 141 | + begin |
| 142 | + # create ni_packet to send to saprouter |
| 143 | + routes = {sap_host => sap_port, ip => port} |
| 144 | + ni_packet = build_ni_packet(routes) |
| 145 | + |
| 146 | + s = connect(false, |
| 147 | + { |
| 148 | + 'RPORT' => sap_port, |
| 149 | + 'RHOST' => sap_host |
| 150 | + } |
| 151 | + ) |
| 152 | + |
| 153 | + s.write(ni_packet, ni_packet.length) |
| 154 | + response = s.get() |
| 155 | + |
| 156 | + res = parse_response_packet(response, ip, port) |
| 157 | + if res |
| 158 | + r << res |
| 159 | + end |
| 160 | + |
| 161 | + rescue ::Rex::ConnectionRefused |
| 162 | + print_error("#{ip}:#{port} - Unable to connect to SAPRouter #{sap_host}:#{sap_port} - Connection Refused") |
| 163 | + |
| 164 | + rescue ::Rex::ConnectionError, ::IOError, ::Timeout::Error |
| 165 | + rescue ::Rex::Post::Meterpreter::RequestError |
| 166 | + rescue ::Interrupt |
| 167 | + raise $! |
| 168 | + ensure |
| 169 | + disconnect(s) rescue nil |
| 170 | + end |
| 171 | + end |
| 172 | + end |
| 173 | + thread.each { |x| x.join } |
| 174 | + |
| 175 | + rescue ::Timeout::Error |
| 176 | + ensure |
| 177 | + thread.each { |x| x.kill rescue nil } |
| 178 | + end |
| 179 | + |
| 180 | + r.each do |res| |
| 181 | + report_service(:host => res[0], :port => res[1], :state => res[2]) |
| 182 | + end |
| 183 | + |
| 184 | + end |
| 185 | + |
| 186 | +end |
0 commit comments