Implement native DNS for Msf Namespace
Built atop the Rex::Proto::DNS work to implement mixins for client and server functionality, providing common interfaces for querying domain name servers, and providing domain name services to clients across Rex sockets. Fully functional native DNS server module is included to demonstrate functionality, serve as a spoofing DNS server, a collecting proxy, or any other number of DNS functions. ----- At the core of this work is a Rex::Proto::DNS::Resolver object descended from Net::DNS::Resolver with overrides and alterations for using Rex sockets. The sockets implementation has been in use internally for a number of years and is well tested. Changes have been made to provider better interface for higher level components. The resolver provides forward lookup capability for the server (Rex::Proto::DNS::Server) which also implements a self-pruning Cache subclass capable of holding static entries. The server can operate in TCP or UDP mode, and provides a common abstraction for addressing TCP and UDP clients by passing a Rex::Socket::Udp mock client around with the data object to higher level consumers. Finally, as is standard practice when building full service objects from Rex to Msf, the server allows consumers to efficiently take execution control at the request and response handlers by passing Procs into the constructor (or manually assigning at runtime) for execution instead of the default call chain. The service, lookup, and caching functionality is encapsulated and stands on its own to be used by consumers other than the standard Msf::Exploit::Remote namespaces. It is intended to serve as the driver and transport handler for pending DNS tunnel transports, and can be used by exploit and auxiliary modules directly. ----- The Msf::Exploit::Remote namespace receives DNS, DNS::Client, and DNS::Server mixins providing common interfaces for Rex::Proto::DNS objects. These mixins create convenience methods for executing queries, serving requests, and configuring the Rex providers. DNS::Client mixin attempts to "intelligently" configure the client resolver's name servers and options from the data store. Accessor, query, and configuration methods are provided in this mixin. Of note are the wildcard and switchdns methods which were adapted from prior work by others (likely Carlos Perez) which can be used by numerous consumer modules. Consumers should use setup_client during their run call to ensure the resolver is appropriately configured. DNS::Server mixin creates common service wrappers for modules to utilize along with a configuration mechanism analagous to the one used by the Client mixin, called setup_server, and calling the setup_client method if present. Note that when setup_server is called, the consumer does not need to call setup_resolver. ------ At the framework module level, a native dns server is provided to showcase the mixin functionality and provide everything from normal DNS services, to tunneling proxies (with cache disabled), spoofing services, and MITM functionality via the handler Procs for requests and responses. Use auxiliary/server/dns/native_server to get started. ----- Testing: Basic local testing completed. Needs to be checked for info leaks - we used to leak a lot. Needs to be checked for functionality under varying configs. Notes: We have a serious problem with the datastore somewhere in the Msf namespace. Datastore options must be validated with options.validate(datastore) or they are all Strings, which completely destroys any type-dependent logic consuming datastore values. This must be addressed separately and all calls to options.validate(datastore) should be removed (other work has included such calls as well, this just proved that the problem exists upstream). Future work: Implement sessions transports atop the DNS infrastructure in order to provide native DNS tunneling.MS-2855/keylogger-mettle-extension
parent
b5c89c4ffe
commit
2f0003b5bd
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This namespace exposes methods for interacting with and providing services
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::DNS
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'msf/core/exploit/dns/client'
|
||||
require 'msf/core/exploit/dns/server'
|
|
@ -0,0 +1,249 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module exposes methods for querying a remote DNS service
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::DNS
|
||||
module Client
|
||||
|
||||
include Exploit::Remote::Udp
|
||||
include Exploit::Remote::Tcp
|
||||
|
||||
MATCH_HOSTNAME = Rex::Proto::DNS::Constants::MATCH_HOSTNAME
|
||||
|
||||
#
|
||||
# Initializes an exploit module that interacts with a DNS server.
|
||||
#
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
deregister_options('RHOST')
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(53),
|
||||
Opt::Proxies,
|
||||
OptString.new('DOMAIN', [ false, "The target domain name"]),
|
||||
OptString.new('NS', [ false, "Specify the nameservers to use for queries, space separated" ]),
|
||||
OptString.new('SEARCHLIST', [ false, "DNS domain search list, comma separated"]),
|
||||
OptInt.new('THREADS', [true, "Number of threads to use in threaded queries", 1])
|
||||
], Exploit::Remote::DNS::Client
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('DNS::Client::DEFAULT_NS', [ false, "Specify the default to use for queries, space separated", '8.8.8.8 8.8.4.4' ]),
|
||||
OptInt.new('DNS::Client::RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]),
|
||||
OptInt.new('DNS::Client::RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]),
|
||||
OptBool.new('DNS::Client::REPORT_A_RECORDS', [false, "Add hosts found via BRT and RVL to DB", true]),
|
||||
OptBool.new('DNS::Client::RVL_EXISTING_ONLY', [false, "Only perform lookups on hosts in DB", true]),
|
||||
OptBool.new('DNS::Client::TCP_DNS', [false, "Run queries over TCP", false]),
|
||||
OptPath.new('DNS::Client::RESOLVCONF', [true, "Resolvconf formatted configuration file to use for Resolver", "/dev/null"])
|
||||
], Exploit::Remote::DNS::Client
|
||||
)
|
||||
|
||||
register_autofilter_ports([ 53 ])
|
||||
register_autofilter_services(%W{ dns })
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Convenience wrapper around Resolver's query method - send DNS request
|
||||
#
|
||||
# @param domain [String] Domain for which to request a record
|
||||
# @param type [String] Type of record to request for domain
|
||||
#
|
||||
# @return [Net::DNS::RR] DNS response
|
||||
def query(domain = datastore['DOMAIN'], type = 'A')
|
||||
client.query(domain, type)
|
||||
end
|
||||
|
||||
#
|
||||
# Performs a set of asynchronous lookups for an array of domain,type pairs
|
||||
#
|
||||
# @param queries [Array] Set of domain,type pairs to pass into #query
|
||||
# @param threadmax [Fixnum] Max number of running threads at a time
|
||||
# @param block [Proc] Code block to execute with the query result
|
||||
#
|
||||
# @return [Array] Resulting set of responses or responses processed by passed blocks
|
||||
def query_async(queries = [], threadmax = datastore['THREADS'], &block)
|
||||
running = []
|
||||
while !queries.empty?
|
||||
domain, type = queries.shift
|
||||
running << framework.threads.spawn("Module(#{self.refname})-#{domain} #{type}", false) do |qat|
|
||||
if block
|
||||
block.call(query(domain,type))
|
||||
else
|
||||
query(domain,type)
|
||||
end
|
||||
end
|
||||
while running.map(:alive?).count >= threadmax
|
||||
Rex::ThreadSafe.sleep(1)
|
||||
end
|
||||
end
|
||||
return running.join
|
||||
end
|
||||
|
||||
#
|
||||
# Switch DNS forwarders in resolver with thread safety
|
||||
#
|
||||
# @param ns [Array, String] List of (or single) nameservers to use
|
||||
def set_nameserver(ns = [])
|
||||
if ns.respond_to?(:split)
|
||||
ns = [ns]
|
||||
end
|
||||
@lock.synchronize do
|
||||
@dns_resolver.nameserver = ns.flatten
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Switch nameservers to use explicit NS or SOA for target
|
||||
#
|
||||
# @param domain [String] Domain for which to find SOA
|
||||
def switchdns(domain)
|
||||
if not datastore['NS'].nil?
|
||||
vprint_status("Using DNS Server: #{client.nameserver.join(', ')}")
|
||||
client.nameserver = process_nameservers
|
||||
else
|
||||
resp_soa = client.query(target, "SOA")
|
||||
if (resp_soa)
|
||||
(resp_soa.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr|
|
||||
resp_1_soa = client.search(rr.mname)
|
||||
if (resp_1_soa and resp_1_soa.answer[0])
|
||||
set_nameserver(resp_1_soa.answer.map(&:address).compact.map(&:to_s))
|
||||
print_status("Set DNS Server to #{target} NS: #{client.nameserver.join(', ')}")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Detect if target has wildcards enabled for a record type
|
||||
#
|
||||
# @param target [String] Domain to test
|
||||
# @param type [String] Record type to test
|
||||
#
|
||||
# @return [String] Address which is returned for wildcard requests
|
||||
def wildcard(domain, type = "A")
|
||||
addr = false
|
||||
rendsub = rand(10000).to_s
|
||||
response = query("#{rendsub}.#{target}", type)
|
||||
if response.answer.length != 0
|
||||
vprint_status("This domain has wildcards enabled!!")
|
||||
response.answer.each do |rr|
|
||||
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME
|
||||
addr = rr.address.to_s
|
||||
end
|
||||
end
|
||||
return addr
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the resolver
|
||||
#
|
||||
def client
|
||||
@dns_resolver
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the target host
|
||||
#
|
||||
def rhost
|
||||
datastore['RHOST']
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the remote port
|
||||
#
|
||||
def rport
|
||||
datastore['RPORT']
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the Host and Port as a string
|
||||
#
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the configured proxy list
|
||||
#
|
||||
def proxies
|
||||
datastore['Proxies']
|
||||
end
|
||||
|
||||
#
|
||||
# Create and configure Resolver object
|
||||
#
|
||||
def setup_resolver
|
||||
options.validate(datastore) # This is a hack, DS values should not be Strings prior to this
|
||||
config = {
|
||||
:config_file => datastore['DNS::Client::RESOLVCONF'],
|
||||
:nameservers => process_nameservers,
|
||||
:port => datastore['RPORT'],
|
||||
:retry_number => datastore['DNS::Client::RETRY'].to_i,
|
||||
:retry_interval => datastore['DNS::Client::RETRY_INTERVAL'].to_i,
|
||||
:use_tcp => datastore['DNS::Client::TCP_DNS'],
|
||||
:context => {'Msf' => framework, 'MsfExploit' => self}
|
||||
}
|
||||
if datastore['SEARCHLIST']
|
||||
if datastore['SEARCHLIST'].split(',').all? do |search|
|
||||
search.match(MATCH_HOSTNAME)
|
||||
end
|
||||
config[:search_list] = datastore['SEARCHLIST'].split(',')
|
||||
else
|
||||
raise 'Domain search list must consist of valid domains'
|
||||
end
|
||||
end
|
||||
if datastore['CHOST']
|
||||
config[:source_address] = IPAddr.new(datastore['CHOST'].to_s)
|
||||
end
|
||||
if datastore['CPORT']
|
||||
config[:source_port] = datastore['CPORT'] unless datastore['CPORT'] == 0
|
||||
end
|
||||
if datastore['Proxies']
|
||||
vprint_status("Using DNS/TCP resolution for proxy config")
|
||||
config[:use_tcp] = true
|
||||
config[:proxies] = datastore['Proxies']
|
||||
end
|
||||
@dns_resolver = Rex::Proto::DNS::Resolver.new(config)
|
||||
@dns_resolver_lock = Mutex.new unless @dns_resolver_lock
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the resolver's nameservers
|
||||
# Uses explicitly defined NS option if set
|
||||
# Uses RHOSTS if not explicitly defined
|
||||
def process_nameservers
|
||||
if datastore['NS'] and !datastore['NS'].empty?
|
||||
if datastore['NS'].split(/\s|,/).all? do |nameserver|
|
||||
Rex::Socket.it_ip_addr?(nameserver)
|
||||
end
|
||||
return datastore['NS'].split(/\s|,/)
|
||||
else
|
||||
raise "Nameservers must be proper IP addresses"
|
||||
end
|
||||
else
|
||||
ns = []
|
||||
rw = Rex::Socket::RangeWalker.new(datastore['DNS::Client::DEFAULT_NS'].split(/\s|,/))
|
||||
while addr = rw.next_ip
|
||||
# break if addr.nil?
|
||||
ns << addr
|
||||
end
|
||||
return ns.compact
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,176 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module exposes methods for querying a remote DNS service
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::DNS
|
||||
module Server
|
||||
|
||||
MATCH_HOSTNAME = Rex::Proto::DNS::Constants::MATCH_HOSTNAME
|
||||
|
||||
#
|
||||
# Initializes an exploit module that serves DNS requests
|
||||
#
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptAddress.new('SRVHOST', [ true, "The local host to listen on.", '0.0.0.0' ]),
|
||||
OptPort.new('SRVPORT', [true, 'The local port to listen on.', 53]),
|
||||
OptString.new('STATIC_ENTRIES', [ false, "DNS domain search list (hosts file or space/semicolon separate entries)"]),
|
||||
OptBool.new('DISABLE_RESOLVER', [ false, "Disable DNS request forwarding", false]),
|
||||
OptBool.new('DISABLE_NS_CACHE', [ false, "Disable DNS response caching", false])
|
||||
], Exploit::Remote::DNS::Server
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('DNS::Server::UDP_DNS', [true, "Serve UDP DNS requests", true]),
|
||||
OptBool.new('DNS::Server::TCP_DNS', [true, "Serve TCP DNS requests", false])
|
||||
], Exploit::Remote::DNS::Server
|
||||
)
|
||||
end
|
||||
|
||||
attr_accessor :service
|
||||
|
||||
#
|
||||
# Process static entries
|
||||
#
|
||||
# @param entries [String] Filename or String containing static entries
|
||||
# @param type [String] Type of record for which to add static entries
|
||||
#
|
||||
# @return [Array] List of static entries in the cache
|
||||
def add_static_hosts(entries = datastore['STATIC_ENTRIES'], type = 'A')
|
||||
if File.file?(File.expand_path(entries))
|
||||
data = File.read(File.expand_path(entries)).split("\n")
|
||||
else
|
||||
data = entries.split(';')
|
||||
end
|
||||
data.each do |entry|
|
||||
next if data.gsub(/\s/,'').empty?
|
||||
addr, names = entry.split(' ', 2)
|
||||
names.split.each do |name|
|
||||
server.cache.add_static(name, addr, type)
|
||||
end
|
||||
end
|
||||
data.cache.records.select {|r,e| e == 0}
|
||||
end
|
||||
|
||||
#
|
||||
# Flush all static entries
|
||||
#
|
||||
def flush_static_hosts
|
||||
data.cache.records.select {|r,e| e == 0}.each do |flush|
|
||||
data.cache.delete(flush)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Flush cache entries
|
||||
# @param static [TrueClass, FalseClass] flush static hosts
|
||||
def flush_cache(static = false)
|
||||
self.service.cache.stop(true)
|
||||
flush_static_hosts if static
|
||||
self.service.cache.start
|
||||
end
|
||||
|
||||
#
|
||||
# Starts the server
|
||||
#
|
||||
def start_service
|
||||
setup_server
|
||||
self.service = @dns_server
|
||||
self.service.start(!datastore['DISABLE_NS_CACHE'])
|
||||
end
|
||||
|
||||
#
|
||||
# Stops the server
|
||||
# @param destroy [TrueClass,FalseClass] Dereference the server object
|
||||
def stop_service(destroy = false)
|
||||
self.service.stop unless self.service.nil?
|
||||
self.service = nil
|
||||
@dns_server = nil if destroy
|
||||
end
|
||||
|
||||
#
|
||||
# Resets the DNS server
|
||||
#
|
||||
def reset_service
|
||||
stop_service
|
||||
@dns_server = nil
|
||||
start_service
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the server
|
||||
#
|
||||
def service
|
||||
@dns_server
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the local host that is being listened on.
|
||||
#
|
||||
def srvhost
|
||||
datastore['SRVHOST']
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the local port that is being listened on.
|
||||
#
|
||||
def srvport
|
||||
datastore['SRVPORT']
|
||||
end
|
||||
|
||||
#
|
||||
# Handle incoming requests
|
||||
# Override this method in modules to take flow control
|
||||
#
|
||||
def handle_request
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Handle incoming requests
|
||||
# Override this method in modules to take flow control
|
||||
#
|
||||
def handle_response
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Create and configure Server object unless one exists
|
||||
# Pass in existing resolver if available and allowed
|
||||
# Create Procs for request and response handlers if defined
|
||||
#
|
||||
def setup_server
|
||||
return if @dns_server
|
||||
options.validate(datastore) # This is a hack, DS values should not be Strings prior to this
|
||||
if !datastore['DISABLE_RESOLVER'] and self.respond_to?(:setup_resolver)
|
||||
setup_resolver
|
||||
end
|
||||
@dns_server = Rex::Proto::DNS::Server.new(
|
||||
datastore['SRVHOST'],
|
||||
datastore['SRVPORT'],
|
||||
datastore['DNS::Server::UDP_DNS'],
|
||||
datastore['DNS::Server::TCP_DNS'],
|
||||
(datastore['DISABLE_RESOLVER'] ? false : @dns_resolver),
|
||||
nil,
|
||||
{'Msf' => framework, 'MsfExploit' => self},
|
||||
handle_request,
|
||||
handle_response
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/exploit/dns'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::DNS::Client
|
||||
include Msf::Exploit::Remote::DNS::Server
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Native DNS Server (Example)',
|
||||
'Description' => %q{
|
||||
This module provides a Rex based DNS service which can store static entries,
|
||||
resolve names over pivots, and serve DNS requests across routed session comms.
|
||||
DNS tunnels can operate across the the Rex switchboard, and DNS other modules
|
||||
can use this as a template. Setting static records via hostfile allows for DNS
|
||||
spoofing attacks without direct traffic manipulation at the handlers.
|
||||
},
|
||||
'Author' => 'RageLtMan <rageltman[at]sempervictus>',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [],
|
||||
'DisclosureDate' => 'November 1987' # RFC 1035
|
||||
))
|
||||
end
|
||||
|
||||
#
|
||||
# Wrapper for service execution and cleanup
|
||||
#
|
||||
def run
|
||||
begin
|
||||
setup_server
|
||||
start_service
|
||||
while service.running?
|
||||
Rex::ThreadSafe.sleep(1)
|
||||
end
|
||||
ensure
|
||||
stop_service
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Creates Proc to handle incoming requests
|
||||
#
|
||||
def handle_request
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Creates Proc to handle outbound responses
|
||||
#
|
||||
def handle_response
|
||||
Proc.new do |cli, data|
|
||||
cli.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
Loading…
Reference in New Issue