diff --git a/lib/msf/core/auxiliary/mixins.rb b/lib/msf/core/auxiliary/mixins.rb index 0274845b69..f90a3f1d2e 100644 --- a/lib/msf/core/auxiliary/mixins.rb +++ b/lib/msf/core/auxiliary/mixins.rb @@ -11,6 +11,7 @@ require 'msf/core/auxiliary/dos' require 'msf/core/auxiliary/fuzzer' require 'msf/core/auxiliary/report' require 'msf/core/auxiliary/scanner' +require 'msf/core/auxiliary/udp_scanner' require 'msf/core/auxiliary/timed' require 'msf/core/auxiliary/wmapmodule' require 'msf/core/auxiliary/crawler' diff --git a/modules/auxiliary/scanner/discovery/udp_sweep.rb b/modules/auxiliary/scanner/discovery/udp_sweep.rb index 881e1584c0..7ac5b1c0c3 100644 --- a/modules/auxiliary/scanner/discovery/udp_sweep.rb +++ b/modules/auxiliary/scanner/discovery/udp_sweep.rb @@ -16,23 +16,17 @@ require 'openssl' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner + include Msf::Auxiliary::UDPScanner def initialize super( 'Name' => 'UDP Service Sweeper', 'Version' => '$Revision$', - 'Description' => 'Detect common UDP services', + 'Description' => 'Detect interesting UDP services', 'Author' => 'hdm', 'License' => MSF_LICENSE ) - register_options( - [ - Opt::CHOST, - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), - ], self.class) - register_advanced_options( [ OptBool.new('RANDOMIZE_PORTS', [false, 'Randomize the order the ports are probed', true]) @@ -54,7 +48,6 @@ class Metasploit3 < Msf::Auxiliary @probes << 'probe_pkt_citrix' @probes << 'probe_pkt_pca_st' @probes << 'probe_pkt_pca_nq' - end def setup @@ -65,87 +58,23 @@ class Metasploit3 < Msf::Auxiliary end end - - # Define our batch size - def run_batch_size - datastore['BATCHSIZE'].to_i + def scanner_prescan(batch) + print_status("Sending #{@probes.length} probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @results = {} end - # Fingerprint a single host - def run_batch(batch) - @results = {} - - print_status("Sending #{@probes.length} probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") - - begin - udp_sock = nil - idx = 0 - - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( { 'LocalHost' => datastore['CHOST'] || nil, 'Context' => {'Msf' => framework, 'MsfExploit' => self} }) - add_socket(udp_sock) - - # Send each probe to each host - @probes.each do |probe| - batch.each do |ip| - begin - data, port = self.send(probe, ip) - udp_sock.sendto(data, ip, port, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - if (idx % 30 == 0) - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - reply_addr = r[1].split(':').last - parse_reply(r) if batch.include? reply_addr - end - end - - idx += 1 - end - end - - cnt = 0 - del = 10 - sts = Time.now.to_i - while (r = udp_sock.recvfrom(65535, del) and r[1]) - reply_addr = r[1].split(':').last - parse_reply(r) if batch.include? reply_addr - - # Prevent an indefinite loop if the targets keep replying - cnt += 1 - break if cnt > run_batch_size - - # Escape after 15 seconds regardless of batch size - break if ((sts + 15) < Time.now.to_i) - - del = 1.0 - end - - rescue ::Interrupt - raise $! - rescue ::Errno::ENOBUFS - print_status("Socket buffers are full, waiting for them to flush...") - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - reply_addr = r[1].split(':').last - parse_reply(r) if batch.include? reply_addr - end - select(nil, nil, nil, 0.25) - retry - rescue ::Exception => e - print_error("Unknown error: #{e.class} #{e}") + def scan_host(ip) + @probes.each do |probe| + data, port = self.send(probe, ip) + scanner_send(data, ip, port) end + end + def scanner_postscan(batch) @results.each_key do |k| next if not @results[k].respond_to?('keys') data = @results[k] - next unless inside_workspace_boundary?(data[:host]) - conf = { :host => data[:host], :port => data[:port], @@ -165,43 +94,29 @@ class Metasploit3 < Msf::Auxiliary report_service(conf) print_status("Discovered #{data[:app]} on #{k} (#{data[:info]})") end - end - # - # The response parsers - # - def parse_reply(pkt) + def scanner_process(data, shost, sport) - # Ignore "empty" packets - return if not pkt[1] - - if(pkt[1] =~ /^::ffff:/) - pkt[1] = pkt[1].sub(/^::ffff:/, '') - end - - # Ignore duplicates - hkey = "#{pkt[1]}:#{pkt[2]}" - - - app = 'unknown' - inf = '' + hkey = "#{shost}:#{sport}" + app = 'unknown' + inf = '' maddr = nil hname = nil # Work with protocols that return different data in different packets # These are reported at the end of the scanning loop to build state - case pkt[2] + case sport when 5632 @results[hkey] ||= {} data = @results[hkey] data[:app] = "pcAnywhere_stat" - data[:port] = pkt[2] - data[:host] = pkt[1] + data[:port] = sport + data[:host] = shost - case pkt[0] + case data when /^NR(........................)(........)/ name = $1.dup @@ -243,20 +158,20 @@ class Metasploit3 < Msf::Auxiliary # Ignore duplicates return if @results[hkey] - case pkt[2] + case sport when 53 app = 'DNS' ver = nil - if (not ver and pkt[0] =~ /([6789]\.[\w\.\-_\:\(\)\[\]\/\=\+\|\{\}]+)/i) + if (not ver and data =~ /([6789]\.[\w\.\-_\:\(\)\[\]\/\=\+\|\{\}]+)/i) ver = 'BIND ' + $1 end - ver = 'Microsoft DNS' if (not ver and pkt[0][2,4] == "\x81\x04\x00\x01") - ver = 'TinyDNS' if (not ver and pkt[0][2,4] == "\x81\x81\x00\x01") + ver = 'Microsoft DNS' if (not ver and data[2,4] == "\x81\x04\x00\x01") + ver = 'TinyDNS' if (not ver and data[2,4] == "\x81\x81\x00\x01") - ver = pkt[0].unpack('H*')[0] if not ver + ver = data.unpack('H*')[0] if not ver inf = ver if ver @results[hkey] = true @@ -264,30 +179,30 @@ class Metasploit3 < Msf::Auxiliary when 137 app = 'NetBIOS' - data = pkt[0] + buff = data.dup - head = data.slice!(0,12) + head = buff.slice!(0,12) xid, flags, quests, answers, auths, adds = head.unpack('n6') return if quests != 0 return if answers == 0 - qname = data.slice!(0,34) - rtype,rclass,rttl,rlen = data.slice!(0,10).unpack('nnNn') - buff = data.slice!(0,rlen) + qname = buff.slice!(0,34) + rtype,rclass,rttl,rlen = buff.slice!(0,10).unpack('nnNn') + bits = buff.slice!(0,rlen) names = [] case rtype when 0x21 - rcnt = buff.slice!(0,1).unpack("C")[0] + rcnt = bits.slice!(0,1).unpack("C")[0] 1.upto(rcnt) do - tname = buff.slice!(0,15).gsub(/\x00.*/, '').strip - ttype = buff.slice!(0,1).unpack("C")[0] - tflag = buff.slice!(0,2).unpack('n')[0] + tname = bits.slice!(0,15).gsub(/\x00.*/, '').strip + ttype = bits.slice!(0,1).unpack("C")[0] + tflag = bits.slice!(0,2).unpack('n')[0] names << [ tname, ttype, tflag ] end - maddr = buff.slice!(0,6).unpack("C*").map{|c| "%.2x" % c }.join(":") + maddr = bits.slice!(0,6).unpack("C*").map{|c| "%.2x" % c }.join(":") names.each do |name| inf << name[0] @@ -309,7 +224,7 @@ class Metasploit3 < Msf::Auxiliary when 111 app = 'Portmap' - buf = pkt[0] + buf = data inf = "" hed = buf.slice!(0,24) svc = [] @@ -317,7 +232,7 @@ class Metasploit3 < Msf::Auxiliary rec = buf.slice!(0,20).unpack("N5") svc << "#{rec[1]} v#{rec[2]} #{rec[3] == 0x06 ? "TCP" : "UDP"}(#{rec[4]})" report_service( - :host => pkt[1], + :host => shost, :port => rec[4], :proto => (rec[3] == 0x06 ? "tcp" : "udp"), :name => "sunrpc", @@ -332,7 +247,7 @@ class Metasploit3 < Msf::Auxiliary when 123 app = 'NTP' ver = nil - ver = pkt[0].unpack('H*')[0] + ver = data.unpack('H*')[0] ver = 'NTP v3' if (ver =~ /^1c06|^1c05/) ver = 'NTP v4' if (ver =~ /^240304/) ver = 'NTP v4 (unsynchronized)' if (ver =~ /^e40/) @@ -343,7 +258,7 @@ class Metasploit3 < Msf::Auxiliary when 1434 app = 'MSSQL' - mssql_ping_parse(pkt[0]).each_pair { |k,v| + mssql_ping_parse(data).each_pair { |k,v| inf += k+'='+v+' ' } @@ -351,7 +266,7 @@ class Metasploit3 < Msf::Auxiliary when 161 app = 'SNMP' - asn = OpenSSL::ASN1.decode(pkt[0]) rescue nil + asn = OpenSSL::ASN1.decode(data) rescue nil return if not asn snmp_error = asn.value[0].value rescue nil @@ -374,29 +289,28 @@ class Metasploit3 < Msf::Auxiliary when 523 app = 'ibm-db2' - inf = db2disco_parse(pkt[0]) + inf = db2disco_parse(data) @results[hkey] = true when 1604 app = 'citrix-ica' - return unless citrix_parse(pkt[0]) + return unless citrix_parse(data) @results[hkey] = true end - return unless inside_workspace_boundary?(pkt[1]) report_service( - :host => pkt[1], + :host => shost, :mac => (maddr and maddr != '00:00:00:00:00:00') ? maddr : nil, :host_name => (hname) ? hname.downcase : nil, - :port => pkt[2], + :port => sport, :proto => 'udp', :name => app, :info => inf, :state => "open" ) - print_status("Discovered #{app} on #{pkt[1]}:#{pkt[2]} (#{inf})") + print_status("Discovered #{app} on #{shost}:#{sport} (#{inf})") end # diff --git a/modules/auxiliary/scanner/netbios/nbname.rb b/modules/auxiliary/scanner/netbios/nbname.rb index dbfb01338f..d965b74e2d 100644 --- a/modules/auxiliary/scanner/netbios/nbname.rb +++ b/modules/auxiliary/scanner/netbios/nbname.rb @@ -16,7 +16,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner + include Msf::Auxiliary::UDPScanner def initialize super( @@ -29,124 +29,58 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - Opt::CHOST, - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), Opt::RPORT(137) ], self.class) end - - # Define our batch size - def run_batch_size - datastore['BATCHSIZE'].to_i - end - - def rport - datastore['RPORT'].to_i - end - - # Fingerprint a single host - def run_batch(batch) - - - print_status("Sending NetBIOS status requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") - + def scanner_prescan(batch) + print_status("Sending NetBIOS requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") @results = {} - begin - udp_sock = nil - idx = 0 + end - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( { 'LocalHost' => datastore['CHOST'] || nil, 'Context' => {'Msf' => framework, 'MsfExploit' => self} }) - add_socket(udp_sock) + def scan_host(ip) + scanner_send(create_netbios_status(ip), ip, datastore['RPORT']) + end - batch.each do |ip| - begin - data = create_netbios_status(ip) - udp_sock.sendto(data, ip, rport, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end + def scanner_postscan(batch) - if (idx % 30 == 0) - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - parse_reply(r) - end - end + cnt = 0 - idx += 1 - end - - while (r = udp_sock.recvfrom(65535, 3) and r[1]) - parse_reply(r) - end - - # Second pass to find additional IPs per host name - - @results.keys.each do |ip| - next if not @results[ip][:name] - begin - data = create_netbios_lookup(@results[ip][:name]) - udp_sock.sendto(data, ip, rport, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - if (idx % 30 == 0) - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - parse_reply(r) - end - end - - idx += 1 - end - - while (r = udp_sock.recvfrom(65535, 3) and r[1]) - parse_reply(r) - end - - rescue ::Interrupt - raise $! - rescue ::Errno::ENOBUFS - print_status("Socket buffers are full, waiting for them to flush...") - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - parse_reply(r) - end - select(nil, nil, nil, 0.25) - rescue ::Exception => e - print_error("Unknown error: #{e.class} #{e} #{e.backtrace}") + # Perform a second pass based on responsive hosts + @results.keys.each do |ip| + next if not @results[ip][:name] + scanner_send(create_netbios_lookup(@results[ip][:name]), ip, datastore['RPORT']) + cnt += 1 end + # Wait for the final replies to trickle in + scanner_recv(10) if cnt > 0 + @results.keys.each do |ip| - next unless inside_workspace_boundary?(ip) + host = @results[ip] user = "" os = "Windows" - if(host[:user] and host[:mac] != "00:00:00:00:00:00") + if (host[:user] and host[:mac] != "00:00:00:00:00:00") user = " User:#{host[:user]}" end - if(host[:mac] == "00:00:00:00:00:00") + if (host[:mac] == "00:00:00:00:00:00") os = "Unix" end names = "" - if(host[:names]) + if (host[:names]) names = " Names:(" + host[:names].map{|n| n[0]}.uniq.join(", ") + ")" end addrs = "" - if(host[:addrs]) + if (host[:addrs]) addrs = "Addresses:(" + host[:addrs].map{|n| n[0]}.uniq.join(", ") + ")" end - if(host[:mac] != "00:00:00:00:00:00") + if (host[:mac] != "00:00:00:00:00:00") report_host(:host => ip, :mac => host[:mac]) else report_host(:host => ip) @@ -174,7 +108,7 @@ class Metasploit3 < Msf::Auxiliary virtual = 'Virtual Computer Inc' end - if(virtual) + if (virtual) extra = "Virtual Machine:#{virtual}" report_note( :host => ip, @@ -183,7 +117,7 @@ class Metasploit3 < Msf::Auxiliary ) end - if(host[:addrs]) + if (host[:addrs]) aliases = [] host[:addrs].map{|n| n[0]}.uniq.each do |addr| next if addr == ip @@ -206,16 +140,7 @@ class Metasploit3 < Msf::Auxiliary end - def parse_reply(pkt) - # Ignore "empty" packets - return if not pkt[1] - - addr = pkt[1] - if(addr =~ /^::ffff:/) - addr = addr.sub(/^::ffff:/, '') - end - - data = pkt[0] + def scanner_process(data, shost, sport) head = data.slice!(0,12) @@ -233,7 +158,7 @@ class Metasploit3 < Msf::Auxiliary hname = nil uname = nil - @results[addr] ||= {} + @results[shost] ||= {} case rtype when 0x21 @@ -248,15 +173,15 @@ class Metasploit3 < Msf::Auxiliary end maddr = buff.slice!(0,6).unpack("C*").map{|c| "%.2x" % c }.join(":") - @results[addr][:names] = names - @results[addr][:mac] = maddr + @results[shost][:names] = names + @results[shost][:mac] = maddr - if (!hname and @results[addr][:names].length > 0) - @results[addr][:name] = @results[addr][:names][0][0] + if (!hname and @results[shost][:names].length > 0) + @results[shost][:name] = @results[shost][:names][0][0] end - @results[addr][:name] = hname if hname - @results[addr][:user] = uname if uname + @results[shost][:name] = hname if hname + @results[shost][:user] = uname if uname inf = '' names.each do |name| @@ -269,24 +194,24 @@ class Metasploit3 < Msf::Auxiliary end end inf << maddr - if inside_workspace_boundary?(addr) - report_service( - :host => addr, - :mac => (maddr and maddr != '00:00:00:00:00:00') ? maddr : nil, - :host_name => (hname) ? hname.downcase : nil, - :port => pkt[2], - :proto => 'udp', - :name => 'netbios', - :info => inf - ) - end + + report_service( + :host => shost, + :mac => (maddr and maddr != '00:00:00:00:00:00') ? maddr : nil, + :host_name => (hname) ? hname.downcase : nil, + :port => datastore['RPORT'], + :proto => 'udp', + :name => 'netbios', + :info => inf + ) + when 0x20 1.upto(rlen / 6.0) do tflag = buff.slice!(0,2).unpack('n')[0] taddr = buff.slice!(0,4).unpack("C*").join(".") names << [ taddr, tflag ] end - @results[addr][:addrs] = names + @results[shost][:addrs] = names end end diff --git a/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb b/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb index d598890afb..37cba8d99d 100644 --- a/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb +++ b/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb @@ -16,7 +16,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner + include Msf::Auxiliary::UDPScanner def initialize super( @@ -33,79 +33,23 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - Opt::CHOST, - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), Opt::RPORT(5632) ], self.class) end - - # Define our batch size - def run_batch_size - datastore['BATCHSIZE'].to_i - end - - def rport - datastore['RPORT'].to_i - end - - # Fingerprint a single host - def run_batch(batch) - + def scanner_prescan(batch) print_status("Sending pcAnywhere discovery requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") - @results = {} - begin - udp_sock = nil - idx = 0 + end - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( { 'LocalHost' => datastore['CHOST'] || nil, 'Context' => {'Msf' => framework, 'MsfExploit' => self} }) - add_socket(udp_sock) - - batch.each do |ip| - begin - # Send network query - udp_sock.sendto("NQ", ip, rport, 0) - - # Send status query - udp_sock.sendto("ST", ip, rport, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - if (idx % 30 == 0) - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - parse_reply(r) - end - end - - idx += 1 - end - - while (r = udp_sock.recvfrom(65535, 3) and r[1]) - parse_reply(r) - end - - rescue ::Interrupt - raise $! - rescue ::Errno::ENOBUFS - print_status("Socket buffers are full, waiting for them to flush...") - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - parse_reply(r) - end - select(nil, nil, nil, 0.25) - rescue ::Exception => e - print_error("Unknown error: #{e.class} #{e} #{e.backtrace}") - end + def scan_host(ip) + scanner_send("NQ", ip, datastore['RPORT']) + scanner_send("ST", ip, datastore['RPORT']) + end + def scanner_postscan(batch) @results.keys.each do |ip| - next unless inside_workspace_boundary?(ip) data = @results[ip] - info = "" if data[:name] @@ -126,17 +70,7 @@ class Metasploit3 < Msf::Auxiliary end end - def parse_reply(pkt) - # Ignore "empty" packets - return if not pkt[1] - - addr = pkt[1] - if(addr =~ /^::ffff:/) - addr = addr.sub(/^::ffff:/, '') - end - - data = pkt[0] - + def scanner_process(data, shost, sport) case data when /^NR(........................)(........)/ @@ -146,12 +80,12 @@ class Metasploit3 < Msf::Auxiliary name = name.gsub(/_+$/, '').gsub("\x00", '').strip caps = caps.gsub(/_+$/, '').gsub("\x00", '').strip - @results[addr] ||= {} - @results[addr][:name] = name - @results[addr][:caps] = caps + @results[shost] ||= {} + @results[shost][:name] = name + @results[shost][:caps] = caps when /^ST(.+)/ - @results[addr] ||= {} + @results[shost] ||= {} buff = $1.dup stat = 'Unknown' @@ -163,9 +97,9 @@ class Metasploit3 < Msf::Auxiliary stat = "Busy" end - @results[addr][:stat] = stat + @results[shost][:stat] = stat else - print_error("#{addr} Unknown: #{data.inspect}") + print_error("#{shost} Unknown: #{data.inspect}") end end diff --git a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb index 2bad9a2c56..61146288fb 100644 --- a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb +++ b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb @@ -14,180 +14,96 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report + include Msf::Auxiliary::UDPScanner def initialize super( - 'Name' => 'SSDP M-SEARCH Gateway Information Discovery', + 'Name' => 'UPnP SSDP M-SEARCH Information Discovery', 'Version' => '$Revision$', - 'Description' => 'Discover information about the local gateway via UPnP', + 'Description' => 'Discover information from UPnP-enabled systems', 'Author' => 'todb', 'License' => MSF_LICENSE ) - register_options( - [ - Opt::CHOST, - Opt::RPORT(1900), - Opt::RHOST("239.255.255.250"), # Generally don't change this. - OptPort.new('SRVPORT', [ false, "The source port to listen for replies.", 0]), - ], self.class - ) - - @result = [] + register_options( [ + Opt::RPORT(1900), + OptBool.new('REPORT_LOCATION', [true, 'This determines whether to report the UPnP endpoint service advertised by SSDP', false ]) + ], self.class) end - def upnp_client_listener() - sock = Rex::Socket::Udp.create( - 'LocalHost' => datastore['CHOST'] || nil, - 'LocalPort' => @sport, - 'Context' => {'Msf' => framework, 'MsfExploit' => self} - ) - add_socket(sock) - while (r = sock.recvfrom(65535, 5) and r[1]) - @result << r + def setup + super + @msearch_probe = + "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + + "ST:upnp:rootdevice\r\n" + + "Man:\"ssdp:discover\"\r\n" + + "MX:3\r\n" + + "\r\n\r\n" # Non-standard, but helps + end + + def scanner_prescan(batch) + print_status("Sending UPnP SSDP probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @results = {} + end + + def scan_host(ip) + scanner_send(@msearch_probe, ip, datastore['RPORT']) + end + + def scanner_process(data, shost, sport) + + skey = "#{shost}:#{datastore['RPORT']}" + return if @results[skey] + + info = [] + if data =~ /^Server:[\s]*(.*)/ + info << $1.strip end - end - def set_server_port - if datastore['SRVPORT'].to_i.zero? - datastore['SRVPORT'] = rand(10_000) + 40_000 - else - datastore['SRVPORT'].to_i - end - end - - def rport - datastore['RPORT'].to_i - end - - def rhost - datastore['RHOST'] - end - - def target - "%s:%d" % [rhost, rport] - end - - # The problem is, the response comes from someplace we're not - # expecting, since we're sending out on the multicast address. - # This means we need to listen on our sending port, either with - # packet craftiness or by being able to set our sport. - def run - - print_status("#{target}: Sending SSDP M-SEARCH Probe.") - @result = [] - - @sport = set_server_port - - begin - udp_send_sock = nil - - server_thread = framework.threads.spawn("Module(#{self.refname})-Listener", false) { upnp_client_listener } - - # TODO: Test to see if this scheme will work when pivoted. - - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_send_sock = Rex::Socket::Udp.create( - 'LocalHost' => datastore['CHOST'] || nil, - 'LocalPort' => @sport, - 'Context' => {'Msf' => framework, 'MsfExploit' => self} - ) - add_socket(udp_send_sock) - data = create_msearch_packet(rhost,rport) - begin - udp_send_sock.sendto(data, rhost, rport, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - begin - Timeout.timeout(6) do - while @result.size.zero? - select(nil, nil, nil, 0.25) - parse_reply @result - end - end - rescue Timeout::Error - end - end - end - - # Someday, take all these very similiar parse_reply functions - # and make them proper block consumers. - def parse_reply(pkts) - pkts.each do |pkt| - # Ignore "empty" packets - return if not pkt[1] - - addr = pkt[1] - if(addr =~ /^::ffff:/) - addr = addr.sub(/^::ffff:/, '') - end - - port = pkt[2] - - data = pkt[0] - info = [] - if data =~ /^Server:[\s]*(.*)/ - server_string = $1 - info << "\"#{server_string.to_s.strip}\"" - end - - ssdp_host = nil - ssdp_port = 80 - if data =~ /^Location:[\s]*(.*)/ - location_string = $1 - info << location_string.to_s.strip - if location_string[/(https?):\x2f\x2f([^\x5c\x2f]+)/] - ssdp_host,ssdp_port = $2.split(":") if $2.respond_to?(:split) - if ssdp_port.nil? - ssdp_port = ($1 == "http" ? 80 : 443) - end + ssdp_host = nil + ssdp_port = 80 + location_string = '' + if data =~ /^Location:[\s]*(.*)/ + location_string = $1 + info << location_string.to_s.strip + if location_string[/(https?):\x2f\x2f([^\x5c\x2f]+)/] + ssdp_host,ssdp_port = $2.split(":") if $2.respond_to?(:split) + if ssdp_port.nil? + ssdp_port = ($1 == "http" ? 80 : 443) end end + end - if data =~ /^USN:[\s]*(.*)/ - usn_string = $1 - info << usn_string.to_s.strip - end + if data =~ /^USN:[\s]*(.*)/ + info << $1.strip + end + return unless info.length > 0 + + desc = info.join(" | ") + + @results[skey] = { + :host => shost, + :port => datastore['RPORT'], + :proto => 'udp', + :name => 'ssdp', + :info => desc + } + + print_status("#{shost}:#{sport} SSDP #{desc}") + report_service( @results[skey] ) + + if ssdp_host report_service( - :host => addr, - :port => port, - :proto => 'udp', - :name => 'ssdp', - :info => info.join("|") - ) - if info.first.nil? || info.first.empty? - print_status "#{addr}:#{port}: Got an incomplete response." - else - print_good "#{addr}:#{port}: Got an SSDP response from #{info.first}" - end - - if ssdp_host - report_service( - :host => ssdp_host, - :port => ssdp_port, - :proto => 'tcp', - :name => 'upnp', - :info => location_string - ) - print_good "#{ssdp_host}:#{ssdp_port}: UPnP services advertised at #{info.grep(/#{ssdp_host}/).first}" - end + :host => ssdp_host, + :port => ssdp_port, + :proto => 'tcp', + :name => 'upnp', + :info => location_string + ) if datastore['REPORT_LOCATION'] end end - # I'm sure this could be a million times cooler. - def create_msearch_packet(host,port) - data = "M-SEARCH * HTTP/1.1\r\n" - data << "Host:#{host}:#{port}\r\n" - data << "ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" - data << "Man:\"ssdp:discover\"\r\n" - data << "MX:3\r\n" - return data - end end