First commit of a udp_mixin and modified scanners
This commit is contained in:
parent
d4fc99e40c
commit
910a91a0f6
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue