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
RageLtMan 2016-02-25 14:26:42 -05:00
parent b5c89c4ffe
commit 2f0003b5bd
4 changed files with 507 additions and 0 deletions

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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