Land #5840, add LLMNR & mDNS modules
commit
9f9bbce034
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/auxiliary/mdns'
|
||||
|
||||
module Msf
|
||||
# This module provides methods for working with LLMNR
|
||||
module Auxiliary::LLMNR
|
||||
include Auxiliary::MDNS
|
||||
|
||||
# Initializes an instance of an auxiliary module that uses LLMNR
|
||||
def initialize(info = {})
|
||||
super
|
||||
register_options(
|
||||
[
|
||||
OptAddressRange.new('RHOSTS', [true, 'The multicast address or CIDR range of targets to query', '224.0.0.252']),
|
||||
Opt::RPORT(5355),
|
||||
# TODO: allow more than one
|
||||
OptString.new('NAME', [true, 'The name to query', 'localhost']),
|
||||
OptString.new('TYPE', [true, 'The query type (name, # or TYPE#)', 'A'])
|
||||
],
|
||||
self.class
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,108 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'net/dns'
|
||||
|
||||
module Msf
|
||||
# This module provides methods for working with mDNS
|
||||
module Auxiliary::MDNS
|
||||
# Initializes an instance of an auxiliary module that uses mDNS
|
||||
def initialize(info = {})
|
||||
super
|
||||
register_options(
|
||||
[
|
||||
OptAddressRange.new('RHOSTS', [true, 'The multicast address or CIDR range of targets to query', '224.0.0.251']),
|
||||
Opt::RPORT(5353),
|
||||
OptString.new('NAME', [true, 'The name to query', '_services._dns-sd._udp.local']),
|
||||
OptString.new('TYPE', [true, 'The query type (name, # or TYPE#)', 'PTR']),
|
||||
OptString.new('CLASS', [true, 'The query class (name, # or CLASS#)', 'IN'])
|
||||
],
|
||||
self.class
|
||||
)
|
||||
end
|
||||
|
||||
def setup
|
||||
query_class_name
|
||||
query_type_name
|
||||
end
|
||||
|
||||
def build_probe
|
||||
@probe ||= ::Net::DNS::Packet.new(query_name, query_type_num, query_class_num).data
|
||||
# TODO: support QU vs QM probes
|
||||
# @probe[@probe.size-2] = [0x80].pack('C')
|
||||
# @probe
|
||||
end
|
||||
|
||||
def query_class
|
||||
if datastore['CLASS'] =~ /^\d+$/
|
||||
datastore['CLASS'].to_i
|
||||
else
|
||||
datastore['CLASS'].upcase
|
||||
end
|
||||
end
|
||||
|
||||
def query_class_name
|
||||
Net::DNS::RR::Classes.new(query_class).to_s
|
||||
end
|
||||
|
||||
def query_class_num
|
||||
Net::DNS::RR::Classes.new(query_class).to_i
|
||||
end
|
||||
|
||||
def query_type
|
||||
if datastore['TYPE'] =~ /^\d+$/
|
||||
datastore['TYPE'].to_i
|
||||
else
|
||||
datastore['TYPE'].upcase
|
||||
end
|
||||
end
|
||||
|
||||
def query_name
|
||||
datastore['NAME']
|
||||
end
|
||||
|
||||
def query_type_name
|
||||
Net::DNS::RR::Types.new(query_type).to_s
|
||||
end
|
||||
|
||||
def query_type_num
|
||||
Net::DNS::RR::Types.new(query_type).to_i
|
||||
end
|
||||
|
||||
def describe_response(response)
|
||||
decoded = Resolv::DNS::Message.decode(response)
|
||||
answers = decoded.answer
|
||||
|
||||
if answers.empty? # not sure this will ever happen...
|
||||
"no answers"
|
||||
else
|
||||
# there are often many answers for the same RR, so group them
|
||||
grouped_answers = answers.group_by { |name, _, _| name }
|
||||
# now summarize each group by noting the resource type and the notable
|
||||
# part(s) of that RR
|
||||
summarized_answers = grouped_answers.map do |name, these_answers|
|
||||
summarized_group = these_answers.map do |_, _, data|
|
||||
case data
|
||||
when Resolv::DNS::Resource::IN::A
|
||||
"A #{data.address}"
|
||||
when Resolv::DNS::Resource::IN::AAAA
|
||||
"AAAA #{data.address}"
|
||||
when Resolv::DNS::Resource::IN::PTR
|
||||
"PTR #{data.name}"
|
||||
when Resolv::DNS::Resource::IN::SRV
|
||||
"SRV #{data.target}"
|
||||
when Resolv::DNS::Resource::IN::TXT
|
||||
"TXT #{data.strings.join(',')}"
|
||||
else
|
||||
data.inspect
|
||||
end
|
||||
end
|
||||
"#{name}: (#{summarized_group.join(", ")})"
|
||||
end
|
||||
summarized_answers.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
def request_info
|
||||
"#{query_name} #{query_class}/#{query_type}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,6 +20,8 @@ require 'msf/core/auxiliary/login'
|
|||
require 'msf/core/auxiliary/rservices'
|
||||
require 'msf/core/auxiliary/cisco'
|
||||
require 'msf/core/auxiliary/kademlia'
|
||||
require 'msf/core/auxiliary/llmnr'
|
||||
require 'msf/core/auxiliary/mdns'
|
||||
require 'msf/core/auxiliary/nmap'
|
||||
require 'msf/core/auxiliary/natpmp'
|
||||
require 'msf/core/auxiliary/iax2'
|
||||
|
|
|
@ -181,7 +181,9 @@ module Auxiliary::UDPScanner
|
|||
end
|
||||
|
||||
# Called for each response packet
|
||||
def scanner_process(data, shost, sport)
|
||||
def scanner_process(data, shost, _sport)
|
||||
@results[shost] ||= []
|
||||
@results[shost] << data
|
||||
end
|
||||
|
||||
# Called before the scan block
|
||||
|
|
|
@ -242,7 +242,7 @@ module Msf
|
|||
dev ||= datastore['INTERFACE']
|
||||
dst_mac, src_mac = lookup_eth(dhost, dev)
|
||||
if dst_mac == nil and not bcast
|
||||
return false
|
||||
raise RuntimeError, 'Unable to determine the destination MAC and bcast is false'
|
||||
end
|
||||
inject_eth(:payload => payload, :eth_daddr => dst_mac, :eth_saddr => src_mac)
|
||||
end
|
||||
|
|
|
@ -40,7 +40,7 @@ module Net # :nodoc:
|
|||
when Fixnum
|
||||
return Classes.invert.has_key?(cls)
|
||||
else
|
||||
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
|
||||
raise ClassArgumentError, "Wrong class: #{cls.class}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -55,7 +55,7 @@ module Net # :nodoc:
|
|||
raise ClassArgumentError, "Unknown class number #{cls}"
|
||||
end
|
||||
else
|
||||
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
|
||||
raise ClassArgumentError, "Wrong class: #{cls.class}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -81,7 +81,7 @@ module Net # :nodoc:
|
|||
@str = Classes.invert[@@default]
|
||||
@num = @@default
|
||||
else
|
||||
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
|
||||
raise ClassArgumentError, "Wrong class: #{cls.class}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -89,15 +89,15 @@ module Net # :nodoc:
|
|||
# *PRIVATE* method
|
||||
def new_from_string(cls)
|
||||
case cls
|
||||
when /^CLASS\\d+/
|
||||
# TODO!!!
|
||||
when /^CLASS(\d+)$/
|
||||
new_from_num(Regexp.last_match(1).to_i)
|
||||
else
|
||||
# String with name of class
|
||||
if Classes.has_key? cls
|
||||
@str = cls
|
||||
@num = Classes[cls]
|
||||
else
|
||||
raise ClassesArgumentError, "Unknown cls #{cls}"
|
||||
raise ClassArgumentError, "Unknown class #{cls}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -105,11 +105,13 @@ module Net # :nodoc:
|
|||
# Contructor for numeric data class
|
||||
# *PRIVATE* method
|
||||
def new_from_num(cls)
|
||||
raise ClassArgumentError, "Invalid class #{cls}" if cls < 0 || cls > 0xFFFF
|
||||
if Classes.invert.has_key? cls
|
||||
@num = cls
|
||||
@str = Classes.invert[cls]
|
||||
else
|
||||
raise ClassesArgumentError, "Unknown cls number #{cls}"
|
||||
@num = cls
|
||||
@str = "CLASS#{cls}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -167,8 +167,8 @@ module Net # :nodoc:
|
|||
# *PRIVATE* method
|
||||
def new_from_string(type)
|
||||
case type
|
||||
when /^TYPE\\d+/
|
||||
# TODO!!!
|
||||
when /^TYPE(\d+)$/
|
||||
new_from_num(Regexp.last_match(1).to_i)
|
||||
else
|
||||
# String with name of type
|
||||
if Types.has_key? type
|
||||
|
@ -183,11 +183,13 @@ module Net # :nodoc:
|
|||
# Contructor for numeric data type
|
||||
# *PRIVATE* method
|
||||
def new_from_num(type)
|
||||
raise TypeArgumentError, "Invalid type #{type}" if type < 0 || type > 0xFFFF
|
||||
if Types.invert.has_key? type
|
||||
@num = type
|
||||
@str = Types.invert[type]
|
||||
else
|
||||
raise TypeArgumentError, "Unknown type number #{type}"
|
||||
@num = type
|
||||
@str = "TYPE#{type}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::UDPScanner
|
||||
include Msf::Auxiliary::LLMNR
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'LLMNR Query',
|
||||
'Description' => %q(
|
||||
This module sends LLMNR queries, which are really just normal UDP DNS
|
||||
queries done (usually) over multicast on a different port, 5355.
|
||||
Targets other than the default RHOSTS' 224.0.0.252 should not respond
|
||||
but may anyway.
|
||||
),
|
||||
'Author' =>
|
||||
[
|
||||
'Jon Hart <jon_hart[at]rapid7.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def scanner_prescan(batch)
|
||||
print_status("Sending LLMNR #{query_type_name}/#{query_class_name} queries for #{query_name} to #{batch[0]}->#{batch[-1]} port #{rport} (#{batch.length} hosts)")
|
||||
@results = {}
|
||||
end
|
||||
|
||||
def scanner_postscan(_batch)
|
||||
found = {}
|
||||
@results.each_pair do |peer, resps|
|
||||
resps.each do |resp|
|
||||
found[peer] ||= {}
|
||||
next if found[peer][resp]
|
||||
response_info = describe_response(resp)
|
||||
print_good("#{peer} responded with #{response_info}")
|
||||
report_service(host: peer, port: rport, proto: "udp", name: "llmnr", info: response_info)
|
||||
found[peer][resp] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::UDPScanner
|
||||
include Msf::Auxiliary::MDNS
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'mDNS Query',
|
||||
'Description' => %q(
|
||||
This module sends mDNS queries, which are really just normal UDP DNS
|
||||
queries done (usually) over multicast on a different port, 5353.
|
||||
),
|
||||
'Author' =>
|
||||
[
|
||||
'Jon Hart <jon_hart[at]rapid7.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def scanner_prescan(batch)
|
||||
print_status("Sending mDNS #{query_type_name} #{query_class_name} queries for " \
|
||||
"#{query_name} to #{batch[0]}->#{batch[-1]} port #{rport} (#{batch.length} hosts)")
|
||||
@results = {}
|
||||
end
|
||||
|
||||
def scanner_postscan(_batch)
|
||||
found = {}
|
||||
@results.each_pair do |peer, resps|
|
||||
resps.each do |resp|
|
||||
found[peer] ||= {}
|
||||
next if found[peer][resp]
|
||||
response_info = describe_response(resp)
|
||||
print_good("#{peer} responded with #{response_info}")
|
||||
report_service(host: peer, port: rport, proto: "udp", name: "mdns", info: response_info)
|
||||
found[peer][resp] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'net/dns'
|
||||
|
||||
describe Net::DNS::RR::Classes do
|
||||
|
||||
subject do
|
||||
described_class.new
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
subject(:rr_class) do
|
||||
described_class.allocate
|
||||
end
|
||||
|
||||
it 'raises when initialized with no args' do
|
||||
expect { rr_class.send(:initialize) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'respects default RR class when initialized with a nil RR class' do
|
||||
rr_class.send(:initialize, nil)
|
||||
expect(rr_class.to_i).to eql(1)
|
||||
expect(rr_class.to_s).to eql('IN')
|
||||
end
|
||||
|
||||
# TODO: figure out why this doesn't work
|
||||
skip 'respects configured default RR class' do
|
||||
rr_class.send(:default=, 'NONE')
|
||||
expect(rr_class.to_i).to eql(254)
|
||||
expect(rr_class.to_s).to eql('NONE')
|
||||
end
|
||||
|
||||
it 'initializes with a valid RR class Fixnum argument' do
|
||||
rr_class.send(:initialize, 4)
|
||||
expect(rr_class.to_i).to eql(4)
|
||||
expect(rr_class.to_s).to eql('HS')
|
||||
end
|
||||
|
||||
it 'raises when the supplied RR class Fixnum is invalid' do
|
||||
expect { rr_class.send(:initialize, 123456) }.to raise_error(ClassArgumentError)
|
||||
expect { rr_class.send(:initialize, -1) }.to raise_error(ClassArgumentError)
|
||||
end
|
||||
|
||||
it 'initializes with a valid RR class String argument' do
|
||||
rr_class.send(:initialize, 'CH')
|
||||
expect(rr_class.to_i).to eql(3)
|
||||
expect(rr_class.to_s).to eql('CH')
|
||||
rr_class.send(:initialize, 'CLASS9')
|
||||
expect(rr_class.to_i).to eql(9)
|
||||
expect(rr_class.to_s).to eql('CLASS9')
|
||||
rr_class.send(:initialize, 'CLASS1')
|
||||
expect(rr_class.to_i).to eql(1)
|
||||
expect(rr_class.to_s).to eql('IN')
|
||||
end
|
||||
|
||||
it 'raises when the supplied RR class String is invalid' do
|
||||
expect { rr_class.send(:initialize, 'cats') }.to raise_error(ClassArgumentError)
|
||||
expect { rr_class.send(:initialize, 'CLASS123456') }.to raise_error(ClassArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'net/dns'
|
||||
|
||||
describe Net::DNS::RR::Types do
|
||||
|
||||
subject do
|
||||
described_class.new
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
subject(:rr_type) do
|
||||
described_class.allocate
|
||||
end
|
||||
|
||||
it 'raises when initialized with no args' do
|
||||
expect { rr_type.send(:initialize) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'respects default RR type when initialized with a nil RR type' do
|
||||
rr_type.send(:initialize, nil)
|
||||
expect(rr_type.to_i).to eql(1)
|
||||
expect(rr_type.to_s).to eql('A')
|
||||
end
|
||||
|
||||
# TODO: figure out why this doesn't work
|
||||
skip 'respects configured default RR type' do
|
||||
rr_type.send(:default=, 'CNAME')
|
||||
expect(rr_type.to_i).to eql(5)
|
||||
expect(rr_type.to_s).to eql('CNAME')
|
||||
end
|
||||
|
||||
it 'initializes with a valid RR type Fixnum argument' do
|
||||
rr_type.send(:initialize, 2)
|
||||
expect(rr_type.to_i).to eql(2)
|
||||
expect(rr_type.to_s).to eql('NS')
|
||||
end
|
||||
|
||||
it 'raises when the supplied RR type Fixnum is invalid' do
|
||||
expect { rr_type.send(:initialize, 123456) }.to raise_error(TypeArgumentError)
|
||||
expect { rr_type.send(:initialize, -1) }.to raise_error(TypeArgumentError)
|
||||
end
|
||||
|
||||
it 'initializes with a valid RR type String argument' do
|
||||
rr_type.send(:initialize, 'SRV')
|
||||
expect(rr_type.to_i).to eql(33)
|
||||
expect(rr_type.to_s).to eql('SRV')
|
||||
rr_type.send(:initialize, 'TYPE12')
|
||||
expect(rr_type.to_i).to eql(12)
|
||||
expect(rr_type.to_s).to eql('PTR')
|
||||
rr_type.send(:initialize, 'TYPE123')
|
||||
expect(rr_type.to_i).to eql(123)
|
||||
expect(rr_type.to_s).to eql('TYPE123')
|
||||
end
|
||||
|
||||
it 'raises when the supplied RR type String is invalid' do
|
||||
expect { rr_type.send(:initialize, 'cats') }.to raise_error(TypeArgumentError)
|
||||
expect { rr_type.send(:initialize, 'TYPE123456') }.to raise_error(TypeArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue