From 0f7f86e0f3f172a0663793672522156beeb21abc Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 22 Apr 2014 22:46:38 -0400 Subject: [PATCH 01/40] Changed Gemfile to use RubyDNS 0.7.0. Also removed Sourcify since it's no longer needed. --- Gemfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 22462cbb7..39885aa8b 100644 --- a/Gemfile +++ b/Gemfile @@ -38,8 +38,7 @@ gem "dm-migrations" gem "msfrpc-client" # Metasploit Integration extension #gem "twitter", ">= 5.0.0" # Twitter Notifications extension gem "rubyzip", ">= 1.0.0" -gem "rubydns" # DNS extension -gem "sourcify" +gem "rubydns", "0.7.0" # DNS extension gem "geoip" # geolocation support # For running unit tests From d4ba3ec98c24fb4c870f2cc45db481ba9f4dfd7a Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 22 Apr 2014 22:50:31 -0400 Subject: [PATCH 02/40] Re-enabled DNS extension in config files. --- config.yaml | 2 +- extensions/dns/config.yaml | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/config.yaml b/config.yaml index 72fd6fa28..42cc103d7 100644 --- a/config.yaml +++ b/config.yaml @@ -129,4 +129,4 @@ beef: enable: true # this is still experimental, we're working on it.. dns: - enable: false + enable: true diff --git a/extensions/dns/config.yaml b/extensions/dns/config.yaml index d382ac70f..3733af7ea 100644 --- a/extensions/dns/config.yaml +++ b/extensions/dns/config.yaml @@ -1,15 +1,18 @@ # -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # beef: extension: dns: - enable: false + enable: true name: 'DNS Server' authors: ['soh_cah_toa'] + protocol: 'udp' address: '127.0.0.1' port: 5300 - upstream: - [['tcp', '8.8.8.8', 53], ['udp', '8.8.8.8', 53]] + upstream: [ + ['tcp', '8.8.8.8', 53], + ['udp', '8.8.8.8', 53] + ] From f4d3858af67fd60b96d6bbd0893bf1a0e73cb889 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 22 Apr 2014 22:56:21 -0400 Subject: [PATCH 03/40] Removed unneeded files in ruby/ subdirectory. Changed Logger overrides to disable logging instead of using BeEF's print_* methods. RubyDNS logging is too verbose. The DNS extension will perform debug logging on its own. --- extensions/dns/logger.rb | 15 ++ extensions/dns/ruby.rb | 7 - extensions/dns/ruby/logger.rb | 27 ---- extensions/dns/ruby/rubydns.rb | 253 --------------------------------- 4 files changed, 15 insertions(+), 287 deletions(-) create mode 100644 extensions/dns/logger.rb delete mode 100644 extensions/dns/ruby.rb delete mode 100644 extensions/dns/ruby/logger.rb delete mode 100644 extensions/dns/ruby/rubydns.rb diff --git a/extensions/dns/logger.rb b/extensions/dns/logger.rb new file mode 100644 index 000000000..0033a3b7e --- /dev/null +++ b/extensions/dns/logger.rb @@ -0,0 +1,15 @@ +# +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +# Disables the logger used by RubyDNS due to its excessive verbosity. +class Logger + + def debug(msg); end + def info(msg); end + def error(msg); end + def warn(msg); end + +end diff --git a/extensions/dns/ruby.rb b/extensions/dns/ruby.rb deleted file mode 100644 index 99cf46225..000000000 --- a/extensions/dns/ruby.rb +++ /dev/null @@ -1,7 +0,0 @@ -# -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - http://beefproject.com -# See the file 'doc/COPYING' for copying permission -# -require 'extensions/dns/ruby/logger' -require 'extensions/dns/ruby/rubydns' diff --git a/extensions/dns/ruby/logger.rb b/extensions/dns/ruby/logger.rb deleted file mode 100644 index 3e88cbfa0..000000000 --- a/extensions/dns/ruby/logger.rb +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - http://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -# Overrives the logger used by RubyDNS to use BeEF's {#print_info} and friends. -class Logger - - def debug(msg) - print_debug "DNS Server: #{msg}" - end - - def info(msg) - print_info "DNS Server: #{msg}" - end - - def error(msg) - print_error "DNS Server: #{msg}" - end - - def warn(msg) - print_error "DNS Server: #{msg}" - end - -end - diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb deleted file mode 100644 index 0a8d13d77..000000000 --- a/extensions/dns/ruby/rubydns.rb +++ /dev/null @@ -1,253 +0,0 @@ -# -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - http://beefproject.com -# See the file 'doc/COPYING' for copying permission -# - -# This module is a modified version of RubyDNS built to be compatible with BeEF. -# For the most part, it will behave exactly the same except where otherwise noted. -# -# Additional features include database support, BeEF logger, assignment of unique -# identifiers to rules, rule removal, and more. -# -# The core functionality of BeEF's DNS server is implemented here, whereas -# BeEF::Extension::Dns::Server is simply a small wrapper around it. -# -# @see http://rubydoc.info/gems/rubydns/frames -module RubyDNS - - # Behaves exactly the same, except without any logger output - def self.run_server(options = {}, &block) - server = RubyDNS::Server.new(&block) - - BeEF::Extension::Dns::Server.instance.set_server(server) - - options[:listen] ||= [[:udp, '0.0.0.0', 53], [:tcp, '0.0.0.0', 53]] - - EventMachine.run do - server.fire(:setup) - - options[:listen].each do |spec| - if spec[0] == :udp - EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, server) - elsif spec[0] == :tcp - EventMachine.start_server(spec[1], spec[2], TCPHandler, server) - end - end - - server.load_rules - server.fire(:start) - end - - server.fire(:stop) - end - - class Server - - class Rule - - attr_accessor :id - - # Now uses an 'id' parameter to uniquely identify rules - def initialize(id, pattern, callback) - @id = id - @pattern = pattern - @callback = callback - end - - end - - # New method that loads all rules from the database at server startup - def load_rules - BeEF::Core::Models::Dns::Rule.each do |rule| - id = rule.id - pattern = [rule.pattern, rule.type] - # antisnatchor: this would be unsafe, but input gets validated in extensions/dns/rest/dns.rb (lines 95 to 105) - # in this case input comes from the DB, but that data stored in the DB was originally coming from the now safe code - block = eval rule.block - - regex = pattern[0] - pattern[0] = Regexp.new(regex) if regex =~ /^\(\?-mix:/ - - @rules << Rule.new(id, pattern, block) - end - end - - # Now includes BeEF database support and checks for already present rules - def match(*pattern, block) - id = '' - - catch :match do - begin - # Sourcify block (already a string only for RESTful API calls) - block_src = case block - when String then - block - when Proc then - block.to_source - end - - # Break out and return id if rule is already present - BeEF::Core::Models::Dns::Rule.each do |rule| - if pattern[0] == rule.pattern && - pattern[1] == rule.type && - block_src == rule.block - - id = rule.id - throw :match - end - end - - id = generate_id - - if @rules == nil - @rules = [] - end - - case block - when String - # antisnatchor: this would be unsafe, but input gets validated in extensions/dns/rest/dns.rb (lines 95 to 105) - @rules << Rule.new(id, pattern, eval(block_src)) - when Proc - @rules << Rule.new(id, pattern, block) - end - - BeEF::Core::Models::Dns::Rule.create( - :id => id, - :pattern => pattern[0].to_s, - :type => pattern[1], - :block => block_src - ) - rescue Sourcify::CannotHandleCreatedOnTheFlyProcError, - Sourcify::CannotParseEvalCodeError, - Sourcify::MultipleMatchingProcsPerLineError, - Sourcify::NoMatchingProcError, - Sourcify::ParserInternalError - - @logger.error "Failed to sourcify block for DNS rule '#{id}'" - raise - end - end - - id - end - - # New method that removes a rule given its id and returns boolean result - def remove_rule(id) - @rules.delete_if { |rule| rule.id == id } - - rule = BeEF::Core::Models::Dns::Rule.get(id) - - rule != nil ? rule.destroy : false - end - - # New method that returns a hash representing the given rule - def get_rule(id) - result = {} - - begin - rule = BeEF::Core::Models::Dns::Rule.get!(id) - - result[:id] = rule.id - result[:pattern] = rule.pattern - result[:type] = rule.type.to_s.split('::')[-1] - result[:response] = parse_response(rule.block) - rescue DataMapper::ObjectNotFoundError => e - @logger.error(e.message) - end - - result - end - - # New method that returns the entire DNS ruleset as an AoH - def get_ruleset - result = [] - - BeEF::Core::Models::Dns::Rule.each do |rule| - element = {} - - element[:id] = rule.id - element[:pattern] = rule.pattern - element[:type] = rule.type.to_s.split('::')[-1] - element[:response] = parse_response(rule.block) - - result << element - end - - result - end - - # New method that removes the entire DNS ruleset - def remove_ruleset - @rules = [] - BeEF::Core::Models::Dns::Rule.destroy - end - - private - - # New method that generates a unique id for a rule - def generate_id - begin - id = BeEF::Core::Crypto.secure_token.byteslice(0..6) - - # Make sure id isn't already in use - BeEF::Core::Models::Dns::Rule.each { |rule| throw StandardError if id == rule.id } - rescue StandardError - retry - end - - id - end - - # New method that parses response callback and returns RDATA as an array - def parse_response(block) - # Extract response arguments into an array - methods = '(respond|failure)' - args = /(?<=\.#{methods}!\().*(?=\))/.match(block).to_s.split(/,\s*/) - - result = [] - - # Determine whether each argument is a domain name, integer, or IP address - args.each do |elem| - arg = nil - - if /Name\.create\((.*)\)/.match(elem) - arg = $1 - elsif /:(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)/.match(elem) - arg = $1.upcase - else - int_test = elem.to_i - arg = (int_test != 0 ? int_test : elem) - end - - arg.gsub!(/['"]/, '') unless arg.is_a?(Integer) - - result << arg - end - - result - end - - end - - class Transaction - - # Behaves exactly the same, except using debug logger instead of info - def respond!(*data) - options = data.last.kind_of?(Hash) ? data.pop : {} - resource_class = options[:resource_class] || @resource_class - - if resource_class == nil - raise ArgumentError, "Could not instantiate resource #{resource_class}!" - end - - @server.logger.debug("Resource class: #{resource_class.inspect}") - resource = resource_class.new(*data) - @server.logger.debug("Resource: #{resource.inspect}") - - append!(resource, options) - end - - end - -end From 7386a7708beb3a8077b47ebebceceb27ad594bad Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 11:14:35 -0400 Subject: [PATCH 04/40] Changed Dns::Server to use RubyDNS 0.7.x API. At this point, it is just a prototype that resolves any request to 1.1.1.1. --- extensions/dns/api.rb | 48 +++++++---- extensions/dns/dns.rb | 160 ++++-------------------------------- extensions/dns/extension.rb | 12 +-- 3 files changed, 54 insertions(+), 166 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 5070a3ac2..cac80f2d6 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -1,5 +1,5 @@ # -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # @@ -11,34 +11,41 @@ module BeEF module NameserverHandler BeEF::API::Registrar.instance.register( - BeEF::Extension::Dns::API::NameserverHandler, - BeEF::API::Server, - 'pre_http_start' + BeEF::Extension::Dns::API::NameserverHandler, + BeEF::API::Server, + 'pre_http_start' ) BeEF::API::Registrar.instance.register( - BeEF::Extension::Dns::API::NameserverHandler, - BeEF::API::Server, - 'mount_handler' + BeEF::Extension::Dns::API::NameserverHandler, + BeEF::API::Server, + 'mount_handler' ) - # Begins main DNS server run-loop at BeEF startup + # Starts the DNS nameserver at BeEF startup. + # + # @param http_hook_server [BeEF::Core::Server] HTTP server instance def self.pre_http_start(http_hook_server) dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns') + dns = BeEF::Extension::Dns::Server.instance + protocol = dns_config['protocol'].to_sym address = dns_config['address'] port = dns_config['port'] + interfaces = [[protocol, address, port]] - dns = BeEF::Extension::Dns::Server.instance - dns.run_server(address, port) + Thread.new { EventMachine.next_tick { dns.run(:listen => interfaces) } } - print_info "DNS Server: #{address}:#{port}" + print_info "DNS Server: #{address}:#{port} (#{protocol})" + + # @todo Upstream servers are not yet supported. Uncomment this section when they are. +=begin servers = [] + unless dns_config['upstream'].nil? dns_config['upstream'].each do |server| - if server[1].nil? or server[2].nil? - next - end + next if server[1].nil? or server[2].nil? + if server[0] == 'tcp' servers << ['tcp', server[1], server[2]] elsif server[0] == 'udp' @@ -46,20 +53,27 @@ module BeEF end end end + if servers.empty? servers << ['tcp', '8.8.8.8', 53] servers << ['udp', '8.8.8.8', 53] end + upstream_servers = '' servers.each do |server| - upstream_servers << "Upstream server: #{server[1]}:#{server[2]} (#{server[0]})\n" + upstream_servers << "Upstream Server: #{server[1]}:#{server[2]} (#{server[0]})\n" end + print_more upstream_servers +=end end - # Mounts handler for processing RESTful API calls + # Mounts the handler for processing DNS RESTful API requests. + # + # @param beef_server [BeEF::Core::Server] HTTP server instance def self.mount_handler(beef_server) - beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new) + # @todo Uncomment when RESTful API is DNS 2.0 compliant. + #beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new) end end diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 6803de477..ac18ce303 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -1,5 +1,5 @@ # -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # @@ -7,156 +7,30 @@ module BeEF module Extension module Dns - # This class is responsible for providing a DNS nameserver that can be dynamically - # configured by other modules and extensions. It is particularly useful for - # performing DNS spoofing, hijacking, tunneling, etc. - # - # Only a single instance will exist during runtime (known as the "singleton pattern"). - # This makes it easier to coordinate actions across the various BeEF systems. - class Server + # @todo Add option for configuring upstream servers. + + # Provides the core DNS nameserver functionality. The nameserver handles incoming requests + # using a rule-based system. A list of user-defined rules is used to match against incoming + # DNS requests. These rules generate a response that is either a resource record or a + # failure code. + class Server < RubyDNS::Server include Singleton - attr_reader :address, :port - - # @!method self.instance - # Returns the singleton instance. Use this in place of {#initialize}. - - # @note This method cannot be invoked! Use {.instance} instead. - # @see ::instance def initialize + super() @lock = Mutex.new - @server = nil end - def set_server(server) - @server = server - end - - def get_server - @server - end - - # Starts the main DNS server run-loop in a new thread. + # Entry point for processing incoming DNS requests. Attempts to find a matching rule and + # sends back its associated response. # - # @param address [String] interface address server should run on - # @param port [Integer] desired server port number - def run_server(address = '0.0.0.0', port = 5300) - @address = address - @port = port - Thread.new do - sleep(2) - - # antisnatchor: RubyDNS is already implemented with EventMachine - run_server_block(@address, @port) - end - end - - # Adds a new DNS rule or "resource record". Does nothing if rule is already present. - # - # @example Adds an A record for foobar.com with the value 1.2.3.4 - # - # dns = BeEF::Extension::Dns::Server.instance - # - # id = dns.add_rule('foobar.com', Resolv::DNS::Resource::IN::A) do |transaction| - # transaction.respond!('1.2.3.4') - # end - # - # @param pattern [String, Regexp] query pattern to recognize - # @param type [Resolv::DNS::Resource::IN] resource record type (e.g. A, CNAME, NS, etc.) - # - # @note When parameter 'pattern' is a literal Regexp object, it must NOT be passed - # using the /.../ literal syntax. Instead use either %r{...} or Regexp::new. - # This does not apply if 'pattern' is a variable. - # - # @yield callback to invoke when pattern is matched - # @yieldparam transaction [RubyDNS::Transaction] details of query question and response - # - # @return [String] unique 7-digit hex identifier for use with {#remove_rule} - # - # @see #remove_rule - # @see http://rubydoc.info/gems/rubydns/RubyDNS/Transaction - def add_rule(pattern, type, &block) - @lock.synchronize { @server.match(pattern, type, block) } - end - - # Removes the given DNS rule. Any future queries for it will be passed through. - # - # @param id [Integer] id returned from {#add_rule} - # - # @return [Boolean] true on success, false on failure - # - # @see #add_rule - def remove_rule(id) - @lock.synchronize { @server.remove_rule(id) } - end - - # Retrieves a specific rule given its id - # - # @param id [Integer] unique identifier for rule - # - # @return [Hash] hash representation of rule - def get_rule(id) - @lock.synchronize { @server.get_rule(id) } - end - - # Returns an AoH representing the entire current DNS ruleset. - # - # Each element is a hash with the following keys: - # - # * :id - # * :pattern - # * :type - # * :response - # - # @return [Array] DNS ruleset (empty if no rules are currently loaded) - def get_ruleset - @lock.synchronize { @server.get_ruleset } - end - - # Clears the entire DNS ruleset. - # - # Requests made after doing so will be passed through to the root nameservers. - # - # @return [Boolean] true on success, false on failure - def remove_ruleset - @lock.synchronize { @server.remove_ruleset } - end - - private - - # Common code needed by {#run_server} to start DNS server. - # - # @param address [String] interface address server should run on - # @param port [Integer] desired server port number - def run_server_block(address, port) - RubyDNS.run_server(:listen => [[:udp, address, port]]) do - # Pass unmatched queries upstream to root nameservers - dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns') - unless dns_config['upstream'].nil? - dns_config['upstream'].each do |server| - if server[1].nil? or server[2].nil? - print_error "Invalid server '#{server[1]}:#{server[2]}' specified for upstream DNS server." - next - elsif server[0] == 'tcp' - servers << [:tcp, server[1], server[2]] - elsif server[0] == 'udp' - servers << [:udp, server[1], server[2]] - else - print_error "Invalid protocol '#{server[0]}' specified for upstream DNS server." - end - end - end - if servers.empty? - print_debug "No upstream DNS servers specified. Using '8.8.8.8'" - servers << [:tcp, '8.8.8.8', 53] - servers << [:udp, '8.8.8.8', 53] - end - otherwise do |transaction| - transaction.passthrough!( - RubyDNS::Resolver.new servers - ) - end + # @param name [String] name of the resource record being looked up + # @param resource [Resolv::DNS::Resource::IN] query type (e.g. A, CNAME, NS, etc.) + # @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer + def process(name, resource, transaction) + @lock.synchronize do + transaction.respond!('1.1.1.1') end end diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index fd909f01b..b7732a576 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -1,5 +1,5 @@ # -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # @@ -18,9 +18,9 @@ module BeEF end end -#TODO antisnatchor: uncomment this when code will be stable -#require 'extensions/dns/api' -#require 'extensions/dns/dns' -#require 'extensions/dns/model' +require 'extensions/dns/api' +require 'extensions/dns/dns' +require 'extensions/dns/logger' +require 'extensions/dns/model' +# @todo Uncomment when RESTful API is DNS 2.0 compliant. #require 'extensions/dns/rest/dns' -#require 'extensions/dns/ruby' From 8c4ece815e603f80a3d24fb23ed2caaa72b55617 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 11:19:17 -0400 Subject: [PATCH 05/40] Removed obsolete Sourcify reference in loader.rb. --- core/loader.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/core/loader.rb b/core/loader.rb index c3929bc59..298494444 100644 --- a/core/loader.rb +++ b/core/loader.rb @@ -16,7 +16,6 @@ require 'base64' require 'xmlrpc/client' require 'openssl' require 'rubydns' -require 'sourcify' # @note Include the filters require 'core/filters' From 861d66207db6247b3dd9a646b5753fd6d0e36844 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 11:27:49 -0400 Subject: [PATCH 06/40] Implemented new Rule model and #add_rule method. --- extensions/dns/dns.rb | 33 +++++++++++++++++++++++++++++++++ extensions/dns/model.rb | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index ac18ce303..8b469505d 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -22,6 +22,39 @@ module BeEF @lock = Mutex.new end + # Adds a new DNS rule. If the rule already exists, its current ID is returned. + # + # @example Adds an A record for browserhacker.com with the IP address 1.2.3.4 + # + # dns = BeEF::Extension::Dns::Server.instance + # + # id = dns.add_rule( + # :pattern => 'browserhacker.com', + # :resource => Resolv::DNS::Resource::IN::A, + # :response => '1.2.3.4' + # ) + # + # @param rule [Hash] hash representation of rule + # @option rule [String, Regexp] :pattern match criteria + # @option rule [Resolv::DNS::Resource::IN] :resource resource record type + # @option rule [String, Array] :response server response + # + # @return [String] unique 8-digit hex identifier + def add_rule(rule = {}) + @lock.synchronize do + # Temporarily disable warnings regarding IGNORECASE flag + verbose = $VERBOSE + $VERBOSE = nil + pattern = Regexp.new(rule[:pattern], Regexp::IGNORECASE) + $VERBOSE = verbose + + BeEF::Core::Models::Dns::Rule.first_or_create( + { :resource => rule[:resource], :pattern => pattern.source }, + { :response => rule[:response] } + ).id + end + end + # Entry point for processing incoming DNS requests. Attempts to find a matching rule and # sends back its associated response. # diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 14f5a2310..062699bdf 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -1,5 +1,5 @@ # -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # @@ -8,16 +8,41 @@ module BeEF module Models module Dns + # Represents an individual DNS rule. class Rule - include DataMapper::Resource storage_names[:default] = 'extension_dns_rules' - property :id, String, :key => true # Unique identifier - property :pattern, Object # Query pattern - property :type, Object # Resource type - property :block, Text # Associated callback + property :id, String, :key => true + property :pattern, Object, :required => true + property :resource, Object, :required => true + property :response, Object, :required => true + property :callback, String, :required => true + + # Hooks the model's "save" event. Generates a rule identifier and callback. + before :save do |rule| + rule.callback = validate_response(rule.resource, rule.response) + rule.id = generate_id + end + + private + # Strict validator which ensures that only an appropriate response is given. + # + # @param resource [Resolv::DNS::Resource::IN] resource record type + # @param response [String, Symbol, Array] response to include in callback + # + # @return [String] string representation of callback that can safely be eval'd + def validate_response(resource, response) + "t.respond!('1.1.1.1')" + end + + # Generates a unique identifier for use as a primary key. + # + # @return [String] 8-character hex identifier + def generate_id + '42' + end end From 007f6302df0e58f1db047f32be51d677e8f7078c Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 11:41:11 -0400 Subject: [PATCH 07/40] Re-implemented #generate_id in Core::Main::Crypto. This is a better home for it since that is where other OpenSSL crypto/token generator methods reside. --- core/main/crypto.rb | 17 +++++++++++++++++ extensions/dns/model.rb | 9 +-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/core/main/crypto.rb b/core/main/crypto.rb index eda268f48..3700fe84e 100644 --- a/core/main/crypto.rb +++ b/core/main/crypto.rb @@ -39,6 +39,23 @@ module Core config.set('beef.api_token', token) token end + + # Generates a unique identifier for DNS rules. + # + # @return [String] 8-character hex identifier + def self.dns_rule_id + id = nil + length = 4 + + begin + id = OpenSSL::Random.random_bytes(length).unpack('H*')[0] + BeEF::Core::Models::Dns::Rule.each { |rule| throw StandardError if id == rule.id } + rescue StandardError + retry + end + + id.to_s + end end end diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 062699bdf..f4b9aef98 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -23,7 +23,7 @@ module BeEF # Hooks the model's "save" event. Generates a rule identifier and callback. before :save do |rule| rule.callback = validate_response(rule.resource, rule.response) - rule.id = generate_id + rule.id = BeEF::Core::Crypto.dns_rule_id end private @@ -37,13 +37,6 @@ module BeEF "t.respond!('1.1.1.1')" end - # Generates a unique identifier for use as a primary key. - # - # @return [String] 8-character hex identifier - def generate_id - '42' - end - end end From b345da0203292ed9db19718f4dae2e60097aafaa Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 12:00:23 -0400 Subject: [PATCH 08/40] Implemented #get_rule method. --- extensions/dns/dns.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 8b469505d..c562dc603 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -55,6 +55,27 @@ module BeEF end end + # Retrieves a specific rule given its identifier. + # + # @param id [Integer] unique identifier for rule + # + # @return [Hash] hash representation of rule (empty hash if rule wasn't found) + def get_rule(id) + @lock.synchronize do + rule = BeEF::Core::Models::Dns::Rule.get(id) + hash = {} + + unless rule.nil? + hash[:id] = rule.id + hash[:pattern] = rule.pattern + hash[:resource] = rule.resource + hash[:response] = rule.response + end + + hash + end + end + # Entry point for processing incoming DNS requests. Attempts to find a matching rule and # sends back its associated response. # From ed986e4ed5c16053d45a3bc4e738cd87946bce57 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 12:07:59 -0400 Subject: [PATCH 09/40] Implemented #remove_rule method. --- extensions/dns/dns.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index c562dc603..2cf90e66d 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -76,6 +76,18 @@ module BeEF end end + # Removes the given DNS rule. + # + # @param id [String] rule identifier + # + # @return [Boolean] true if rule was removed, otherwise false + def remove_rule(id) + @lock.synchronize do + rule = BeEF::Core::Models::Dns::Rule.get(id) + rule.nil? ? false : rule.destroy + end + end + # Entry point for processing incoming DNS requests. Attempts to find a matching rule and # sends back its associated response. # From 56c686de642ea16e6c6b371e6e0f312c030de5e4 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 12:42:39 -0400 Subject: [PATCH 10/40] Implemented #get_ruleset method. Also refactored #get_rule to use new #to_hash helper method since --- extensions/dns/dns.rb | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 2cf90e66d..b0a7dceeb 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -57,22 +57,13 @@ module BeEF # Retrieves a specific rule given its identifier. # - # @param id [Integer] unique identifier for rule + # @param id [String] unique identifier for rule # # @return [Hash] hash representation of rule (empty hash if rule wasn't found) def get_rule(id) @lock.synchronize do rule = BeEF::Core::Models::Dns::Rule.get(id) - hash = {} - - unless rule.nil? - hash[:id] = rule.id - hash[:pattern] = rule.pattern - hash[:resource] = rule.resource - hash[:response] = rule.response - end - - hash + rule.nil? ? {} : to_hash(rule) end end @@ -88,6 +79,20 @@ module BeEF end end + # Returns an AoH representing the entire current DNS ruleset. + # + # Each element is a hash with the following keys: + # + # * :id + # * :pattern + # * :resource + # * :response + # + # @return [Array] DNS ruleset (empty array if no rules are currently defined) + def get_ruleset + @lock.synchronize { BeEF::Core::Models::Dns::Rule.collect { |rule| to_hash(rule) } } + end + # Entry point for processing incoming DNS requests. Attempts to find a matching rule and # sends back its associated response. # @@ -100,6 +105,22 @@ module BeEF end end + private + # Helper method that converts a DNS rule to a hash. + # + # @param rule [BeEF::Core::Models::Dns::Rule] rule to be converted + # + # @return [Hash] hash representation of DNS rule + def to_hash(rule) + hash = {} + hash[:id] = rule.id + hash[:pattern] = rule.pattern + hash[:resource] = rule.resource + hash[:response] = rule.response + + hash + end + end end From 230385149832bc1ab840fa7cdb9bbd60d3fcaa74 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 12:50:42 -0400 Subject: [PATCH 11/40] Implemented #remove_ruleset method. --- extensions/dns/dns.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index b0a7dceeb..30940eff9 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -93,6 +93,13 @@ module BeEF @lock.synchronize { BeEF::Core::Models::Dns::Rule.collect { |rule| to_hash(rule) } } end + # Removes the entire DNS ruleset. + # + # @return [Boolean] true if ruleset was destroyed, otherwise false + def remove_ruleset + @lock.synchronize { BeEF::Core::Models::Dns::Rule.destroy } + end + # Entry point for processing incoming DNS requests. Attempts to find a matching rule and # sends back its associated response. # From 0dd9c193ecb0b30fbbb31e70c8b2087e937d688d Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 12:53:41 -0400 Subject: [PATCH 12/40] Appended ! to #remove_rule and #remove_ruleset. Adhering to the Ruby convention, this indicates that these methods mutate the receiver and, therefore, should be considered dangerous. --- extensions/dns/dns.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 30940eff9..c280adf0d 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -72,7 +72,7 @@ module BeEF # @param id [String] rule identifier # # @return [Boolean] true if rule was removed, otherwise false - def remove_rule(id) + def remove_rule!(id) @lock.synchronize do rule = BeEF::Core::Models::Dns::Rule.get(id) rule.nil? ? false : rule.destroy @@ -96,7 +96,7 @@ module BeEF # Removes the entire DNS ruleset. # # @return [Boolean] true if ruleset was destroyed, otherwise false - def remove_ruleset + def remove_ruleset! @lock.synchronize { BeEF::Core::Models::Dns::Rule.destroy } end From 3029d3cea8717ba493f8a50af49686efbe6bdab0 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 13:28:34 -0400 Subject: [PATCH 13/40] Implemented #process method that handles requests. The DNS server now searches for matching rules and sends its response when handling incoming requests. However, all rules are still assigned the same callback for the moment. --- extensions/dns/dns.rb | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index c280adf0d..3d1bbcdd1 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -108,7 +108,32 @@ module BeEF # @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer def process(name, resource, transaction) @lock.synchronize do - transaction.respond!('1.1.1.1') + print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)}" + + catch (:done) do + # Find rules matching the requested resource class + resources = BeEF::Core::Models::Dns::Rule.all(:resource => resource) + throw :done if resources.length == 0 + + # Narrow down search by finding a matching pattern + resources.each do |rule| + pattern = Regexp.new(rule.pattern) + + if name =~ pattern + print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})" + Proc.new { |t| eval(rule.callback) }.call(transaction) + throw :done + end + end + + # When no match is found, query upstream servers (if enabled) + if @otherwise + print_debug "No match found, querying upstream servers" + @otherwise.call(transaction) + else + print_debug "Failed to handle DNS request for #{name}" + end + end end end @@ -122,12 +147,21 @@ module BeEF hash = {} hash[:id] = rule.id hash[:pattern] = rule.pattern - hash[:resource] = rule.resource + hash[:resource] = format_resource(rule.resource) hash[:response] = rule.response hash end + # Helper method that formats the given resource class in a human-readable format. + # + # @param resource [Resolv::DNS::Resource::IN] resource class + # + # @return [String] resource name stripped of any module/class names + def format_resource(resource) + /::(\w+)$/.match(resource.name)[1] + end + end end From 53a54de5fedbd4846bb66f0207b7179d200ba3a3 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 16:36:16 -0400 Subject: [PATCH 14/40] Added @database to Dns::Server as a model reference. --- extensions/dns/dns.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 3d1bbcdd1..43782af8f 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -20,6 +20,7 @@ module BeEF def initialize super() @lock = Mutex.new + @database = BeEF::Core::Models::Dns::Rule end # Adds a new DNS rule. If the rule already exists, its current ID is returned. @@ -48,7 +49,7 @@ module BeEF pattern = Regexp.new(rule[:pattern], Regexp::IGNORECASE) $VERBOSE = verbose - BeEF::Core::Models::Dns::Rule.first_or_create( + @database.first_or_create( { :resource => rule[:resource], :pattern => pattern.source }, { :response => rule[:response] } ).id @@ -62,7 +63,7 @@ module BeEF # @return [Hash] hash representation of rule (empty hash if rule wasn't found) def get_rule(id) @lock.synchronize do - rule = BeEF::Core::Models::Dns::Rule.get(id) + rule = @database.get(id) rule.nil? ? {} : to_hash(rule) end end @@ -74,7 +75,7 @@ module BeEF # @return [Boolean] true if rule was removed, otherwise false def remove_rule!(id) @lock.synchronize do - rule = BeEF::Core::Models::Dns::Rule.get(id) + rule = @database.get(id) rule.nil? ? false : rule.destroy end end @@ -90,14 +91,14 @@ module BeEF # # @return [Array] DNS ruleset (empty array if no rules are currently defined) def get_ruleset - @lock.synchronize { BeEF::Core::Models::Dns::Rule.collect { |rule| to_hash(rule) } } + @lock.synchronize { @database.collect { |rule| to_hash(rule) } } end # Removes the entire DNS ruleset. # # @return [Boolean] true if ruleset was destroyed, otherwise false def remove_ruleset! - @lock.synchronize { BeEF::Core::Models::Dns::Rule.destroy } + @lock.synchronize { @database.destroy } end # Entry point for processing incoming DNS requests. Attempts to find a matching rule and @@ -112,7 +113,7 @@ module BeEF catch (:done) do # Find rules matching the requested resource class - resources = BeEF::Core::Models::Dns::Rule.all(:resource => resource) + resources = @database.all(:resource => resource) throw :done if resources.length == 0 # Narrow down search by finding a matching pattern From bd9891dc4ddc198ddb70ee05af4bb412ede546c6 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 16:39:26 -0400 Subject: [PATCH 15/40] Implemented #validate_response method in Dns::Rule. Perhaps the ugliest part of the DNS extension, it is also the most crucial. This method ensures that a given resource and response are appropriate for each other. It must also prevent RCE vulns since the input is eval'd later on. However, HINFO, MINFO, and especially TXT validation is not strict enough. These three need to be reviewed scrupulously since a 100% anti-RCE solution may prove to be difficult. --- extensions/dns/model.rb | 228 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index f4b9aef98..87d734a16 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -34,7 +34,233 @@ module BeEF # # @return [String] string representation of callback that can safely be eval'd def validate_response(resource, response) - "t.respond!('1.1.1.1')" + domain_regex = /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,})$/i + sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i + + ipv4_regex = /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/x + + ipv6_regex = /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,7}:| + ([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}| + ([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}| + ([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}| + ([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}| + [0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})| + :((:[0-9a-f]{1,4}){1,7}|:)| + fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}| + ::(ffff(:0{1,4}){0,1}:){0,1} + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| + ([0-9a-f]{1,4}:){1,4}: + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix + + begin + src = if resource == Resolv::DNS::Resource::IN::A + if response.is_a?(String) && response =~ ipv4_regex + sprintf "t.respond!('%s')", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + elsif response.is_a?(Array) + str1 = "t.respond!('%s');" + str2 = '' + + response.each do |r| + raise InvalidDnsResponseError, 'A' unless r =~ ipv4_regex + str2 << sprintf(str1, r) + end + + str2 + else + raise InvalidDnsResponseError, 'A' + end + elsif resource == Resolv::DNS::Resource::IN::AAAA + if response.is_a?(String) && response =~ ipv6_regex + sprintf "t.respond!('%s')", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + elsif response.is_a?(Array) + str1 = "t.respond!('%s');" + str2 = '' + + response.each do |r| + raise InvalidDnsResponseError, 'AAAA' unless r =~ ipv6_regex + str2 << sprintf(str1, r) + end + + str2 + else + raise InvalidDnsResponseError, 'AAAA' + end + elsif resource == Resolv::DNS::Resource::IN::CNAME + if response.is_a?(String) && response =~ domain_regex + sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'CNAME' + end + elsif resource == Resolv::DNS::Resource::IN::HINFO + if response.is_a?(Array) + response.each { |r| raise InvalidDnsResponseError, 'HINFO' unless r.is_a?(String) } + data = { :cpu => response[0], :os => response[1] } + sprintf "t.respond!('%s', '%s')", data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'HINFO' + end + elsif resource == Resolv::DNS::Resource::IN::MINFO + if response.is_a?(Array) + response.each { |r| raise InvalidDnsResponseError, 'MINFO' unless r.is_a?(String) && r =~ domain_regex } + + data = { :rmailbx => response[0], :emailbx => response[1] } + + sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + + "Resolv::DNS::Name.create('%s'))", + data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'MINFO' + end + elsif resource == Resolv::DNS::Resource::IN::MX + if response[0].is_a?(Integer) && + response[1].is_a?(String) && + response[1] =~ domain_regex + + data = { :preference => response[0], :exchange => response[1] } + sprintf "t.respond!(%d, Resolv::DNS::Name.create('%s'))", data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'MX' + end + elsif resource == Resolv::DNS::Resource::IN::NS + if response.is_a?(String) && response =~ domain_regex + sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + elsif response.is_a?(Array) + str1 = "t.respond!(Resolv::DNS::Name.create('%s'))" + str2 = '' + + response.each do |r| + raise InvalidDnsResponseError, 'NS' unless r =~ ipv4_regex + str2 << sprintf(str1, r) + end + + str2 + else + raise InvalidDnsResponseError, 'NS' + end + elsif resource == Resolv::DNS::Resource::IN::PTR + if response.is_a?(String) && response =~ domain_regex + sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'PTR' + end + elsif resource == Resolv::DNS::Resource::IN::SOA + if response.is_a?(Array) + unless response[0].is_a?(String) && + response[0] =~ domain_regex && + response[1].is_a?(String) && + response[1] =~ domain_regex && + response[2].is_a?(Integer) && + response[3].is_a?(Integer) && + response[4].is_a?(Integer) && + response[5].is_a?(Integer) && + response[6].is_a?(Integer) + + raise InvalidDnsResponseError, 'SOA' + end + + data = { + :mname => response[0], + :rname => response[1], + :serial => response[2], + :refresh => response[3], + :retry => response[4], + :expire => response[5], + :minimum => response[6] + } + + sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + + "Resolv::DNS::Name.create('%s'), " + + '%d, ' + + '%d, ' + + '%d, ' + + '%d, ' + + '%d)', + data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'SOA' + end + elsif resource == Resolv::DNS::Resource::IN::TXT + if resource.is_a?(String) + sprintf "t.respond!('%s')", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'TXT' + end + elsif resource == Resolv::DNS::Resource::IN::WKS + if response.is_a?(Array) + unless resource[0].is_a?(String) && + resource[0] =~ ipv4_regex && + resource[1].is_a?(Integer) && + resource[2].is_a?(Integer) + raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String) + end + + data = { + :address => response[0], + :protocol => response[1], + :bitmap => response[2] + } + + sprintf "t.respond!('%
s', %d, %d)", data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'WKS' + end + else + raise UnknownDnsResourceError + end + rescue InvalidDnsResponseError, UnknownDnsResourceError => e + print_error e.message + throw :halt + end + + src + end + + # Raised when a response is not valid for the given DNS resource record. + class InvalidDnsResponseError < StandardError + + def initialize(message = nil) + message = sprintf "Invalid response specified for %s resource record", message unless message.nil? + super(message) + end + + end + + # Raised when an unknown DNS resource record is given. + class UnknownDnsResourceError < StandardError + + DEFAULT_MESSAGE = 'Unknown resource record was given' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end end From 6385ddf85be4f8297481aaace84f72afc8b155c2 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 16:50:47 -0400 Subject: [PATCH 16/40] Changed data type of :callback property to Object. Using String was sufficient but this way is more consistent. --- extensions/dns/model.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 87d734a16..813a5cdd9 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -18,7 +18,7 @@ module BeEF property :pattern, Object, :required => true property :resource, Object, :required => true property :response, Object, :required => true - property :callback, String, :required => true + property :callback, Object, :required => true # Hooks the model's "save" event. Generates a rule identifier and callback. before :save do |rule| From 5d73d7f0842c229b1f7cafd66f42df8ec4c0f990 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 17:49:41 -0400 Subject: [PATCH 17/40] Improved BeEF::Filters support for IPv4 and IPV6. Changed regex in #is_valid_ip? to be more strict since it previously would have matched an invalid IP such as 999.999.999.999. Changed its name to #is_valid_ipv4?. Added new #is_valid_ipv6? method that validates IPv6 addresses. It is very comprehensive and will match normal IPv6 addresses, zero compressed, link-local with zone index, and IPv6 addresses that have IPv4 embedded, mapped, and translated. Added new #is_valid_domain? method that validates domain names. --- core/filters/base.rb | 46 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/core/filters/base.rb b/core/filters/base.rb index 0c96fde20..bc5d4d434 100644 --- a/core/filters/base.rb +++ b/core/filters/base.rb @@ -99,13 +99,47 @@ module Filters only?("a-zA-Z0-9", str) end - # Check if valid ip address string + # Checks if string is a valid IPv4 address # @param [String] ip String for testing - # @return [Boolean] If the string is a valid IP address - # @note only IPv4 compliant - def self.is_valid_ip?(ip) - return false if not is_non_empty_string?(ip) - return true if ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/ + # @return [Boolean] If the string is a valid IPv4 address + def self.is_valid_ipv4?(ip) + return false unless is_non_empty_string?(ip) + return true if ip =~ /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/ + false + end + + # Checks if string is a valid IPv6 address + # @param [String] ip string for testing + # @return [Boolean] If the string is a valid IPv6 address + def self.is_valid_ipv6?(ip) + return false unless is_non_empty_string?(ip) + return true if ip =~ /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,7}:| + ([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}| + ([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}| + ([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}| + ([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}| + [0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})| + :((:[0-9a-f]{1,4}){1,7}|:)| + fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}| + ::(ffff(:0{1,4}){0,1}:){0,1} + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| + ([0-9a-f]{1,4}:){1,4}: + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix + false + end + + # Checks if string is a valid domain name + # @param [String] domain string for testing + # @return [Boolean] If the string is a valid domain name + # @note Only validates the string format. It does not check for a valid TLD since ICANN's list of + # TLD's is not static. + def self.is_valid_domain?(domain) + return false unless is_non_empty_string?(domain) + return true if domain =~ /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,})$/i false end From e791fca8a965cc59a51ac2d86c08e15143f62f70 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 18:07:27 -0400 Subject: [PATCH 18/40] Updated #validate_response to use BeEF::Filters. --- extensions/dns/model.rb | 51 +++++++++++------------------------------ 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 813a5cdd9..910b6cc07 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -34,32 +34,11 @@ module BeEF # # @return [String] string representation of callback that can safely be eval'd def validate_response(resource, response) - domain_regex = /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,})$/i sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i - ipv4_regex = /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/x - - ipv6_regex = /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}| - ([0-9a-f]{1,4}:){1,7}:| - ([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}| - ([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}| - ([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}| - ([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}| - ([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}| - [0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})| - :((:[0-9a-f]{1,4}){1,7}|:)| - fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}| - ::(ffff(:0{1,4}){0,1}:){0,1} - ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| - ([0-9a-f]{1,4}:){1,4}: - ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix - begin src = if resource == Resolv::DNS::Resource::IN::A - if response.is_a?(String) && response =~ ipv4_regex + if response.is_a?(String) && BeEF::Filters.is_valid_ipv4?(response) sprintf "t.respond!('%s')", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex sprintf "t.fail!(:%s)", response.to_sym @@ -68,7 +47,7 @@ module BeEF str2 = '' response.each do |r| - raise InvalidDnsResponseError, 'A' unless r =~ ipv4_regex + raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ipv4?(r) str2 << sprintf(str1, r) end @@ -77,7 +56,7 @@ module BeEF raise InvalidDnsResponseError, 'A' end elsif resource == Resolv::DNS::Resource::IN::AAAA - if response.is_a?(String) && response =~ ipv6_regex + if response.is_a?(String) && BeEF::Filters.is_valid_ipv6(response) sprintf "t.respond!('%s')", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex sprintf "t.fail!(:%s)", response.to_sym @@ -86,7 +65,7 @@ module BeEF str2 = '' response.each do |r| - raise InvalidDnsResponseError, 'AAAA' unless r =~ ipv6_regex + raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ipv6(r) str2 << sprintf(str1, r) end @@ -95,7 +74,7 @@ module BeEF raise InvalidDnsResponseError, 'AAAA' end elsif resource == Resolv::DNS::Resource::IN::CNAME - if response.is_a?(String) && response =~ domain_regex + if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex sprintf "t.fail!(:%s)", response.to_sym @@ -114,7 +93,7 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::MINFO if response.is_a?(Array) - response.each { |r| raise InvalidDnsResponseError, 'MINFO' unless r.is_a?(String) && r =~ domain_regex } + response.each { |r| raise InvalidDnsResponseError, 'MINFO' unless r.is_a?(String) && BeEF::Filters.is_valid_domain?(r) } data = { :rmailbx => response[0], :emailbx => response[1] } @@ -128,8 +107,7 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::MX if response[0].is_a?(Integer) && - response[1].is_a?(String) && - response[1] =~ domain_regex + BeEF::Filters.is_valid_domain?(response[1]) data = { :preference => response[0], :exchange => response[1] } sprintf "t.respond!(%d, Resolv::DNS::Name.create('%s'))", data @@ -139,7 +117,7 @@ module BeEF raise InvalidDnsResponseError, 'MX' end elsif resource == Resolv::DNS::Resource::IN::NS - if response.is_a?(String) && response =~ domain_regex + if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex sprintf "t.fail!(:%s)", response.to_sym @@ -148,7 +126,7 @@ module BeEF str2 = '' response.each do |r| - raise InvalidDnsResponseError, 'NS' unless r =~ ipv4_regex + raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_ipv4?(r) str2 << sprintf(str1, r) end @@ -157,7 +135,7 @@ module BeEF raise InvalidDnsResponseError, 'NS' end elsif resource == Resolv::DNS::Resource::IN::PTR - if response.is_a?(String) && response =~ domain_regex + if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex sprintf "t.fail!(:%s)", response.to_sym @@ -166,10 +144,8 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::SOA if response.is_a?(Array) - unless response[0].is_a?(String) && - response[0] =~ domain_regex && - response[1].is_a?(String) && - response[1] =~ domain_regex && + unless BeEF::Filters.is_valid_domain?(response[0]) && + BeEF::Filters.is_valid_domain?(response[1]) && response[2].is_a?(Integer) && response[3].is_a?(Integer) && response[4].is_a?(Integer) && @@ -212,8 +188,7 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::WKS if response.is_a?(Array) - unless resource[0].is_a?(String) && - resource[0] =~ ipv4_regex && + unless BeEF::Filters.is_valid_ipv4?(resource[0]) && resource[1].is_a?(Integer) && resource[2].is_a?(Integer) raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String) From 3ed4098c2f2247396fb92f65a90bdf5884b63b7b Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 21:47:45 -0400 Subject: [PATCH 19/40] Added unit test assertions for new config options. --- test/unit/extensions/tc_dns.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index bfb7ca2c3..af0ca9e42 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -1,5 +1,5 @@ # -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # @@ -39,8 +39,10 @@ class TC_Dns < Test::Unit::TestCase # Checks for required settings in config file def test_02_config + assert(@@dns_config.has_key?('protocol')) assert(@@dns_config.has_key?('address')) assert(@@dns_config.has_key?('port')) + assert(@@dns_config.has_key?('upstream')) end # Verifies public interface From 820ba3a2e70ab0cf4f673082439f6f5fa90d23bb Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 21:50:23 -0400 Subject: [PATCH 20/40] Updated interface unit tests with new method names. --- test/unit/extensions/tc_dns.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index af0ca9e42..b869219b5 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -49,12 +49,11 @@ class TC_Dns < Test::Unit::TestCase def test_03_interface @@dns = BeEF::Extension::Dns::Server.instance - assert_respond_to(@@dns, :run_server) assert_respond_to(@@dns, :add_rule) - assert_respond_to(@@dns, :remove_rule) assert_respond_to(@@dns, :get_rule) + assert_respond_to(@@dns, :remove_rule!) assert_respond_to(@@dns, :get_ruleset) - assert_respond_to(@@dns, :remove_ruleset) + assert_respond_to(@@dns, :remove_ruleset!) end # Tests that DNS server runs correctly on desired address and port From 01ad87250f261007a0baa009be2fbdbab532444a Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 22:02:19 -0400 Subject: [PATCH 21/40] Removed unnecessary run_server unit tests. Setting the server address:port is now handled by RubyDNS. Therefore, verifying this via unit tests is no longer necessary. --- test/unit/extensions/tc_dns.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index b869219b5..47e12b2d2 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -56,17 +56,7 @@ class TC_Dns < Test::Unit::TestCase assert_respond_to(@@dns, :remove_ruleset!) end - # Tests that DNS server runs correctly on desired address and port - def test_04_run_server - address = @@dns_config['address'] - port = @@dns_config['port'] - - @@dns.run_server(address, port) - sleep(3) - - assert_equal(address, @@dns.address) - assert_equal(port, @@dns.port) - end + # @todo Decrement test numbers starting here. # Tests procedure for properly adding new DNS rules def test_05_add_rule_good From ad25c49b2d6b8b70fb9f497dcd6c6edd7743bf18 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 24 Apr 2014 13:11:00 -0400 Subject: [PATCH 22/40] Refactored IP filters into parameterized #is_valid_ip?. Using parameterized methods is better structured coding style rather than defining multiple similarly-behaved methods. annex_region('crimea') # good vs. annex_crimea # bad --- core/filters/base.rb | 80 +++++++++++++++++++++++------------------ extensions/dns/model.rb | 12 +++---- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/core/filters/base.rb b/core/filters/base.rb index bc5d4d434..3ad37f771 100644 --- a/core/filters/base.rb +++ b/core/filters/base.rb @@ -5,7 +5,7 @@ # module BeEF module Filters - + # Check if the string is not empty and not nil # @param [String] str String for testing # @return [Boolean] Whether the string is not empty @@ -24,7 +24,7 @@ module Filters regex = Regexp.new('[^' + chars + ']') regex.match(str).nil? end - + # Check if one or more characters in 'chars' are in 'str' # @param [String] chars List of characters to match # @param [String] str String for testing @@ -33,7 +33,7 @@ module Filters regex = Regexp.new(chars) not regex.match(str).nil? end - + # Check for null char # @param [String] str String for testing # @return [Boolean] If the string has a null character @@ -98,38 +98,48 @@ module Filters return false if not is_non_empty_string?(str) only?("a-zA-Z0-9", str) end - - # Checks if string is a valid IPv4 address - # @param [String] ip String for testing - # @return [Boolean] If the string is a valid IPv4 address - def self.is_valid_ipv4?(ip) - return false unless is_non_empty_string?(ip) - return true if ip =~ /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/ - false - end - # Checks if string is a valid IPv6 address - # @param [String] ip string for testing - # @return [Boolean] If the string is a valid IPv6 address - def self.is_valid_ipv6?(ip) - return false unless is_non_empty_string?(ip) - return true if ip =~ /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}| - ([0-9a-f]{1,4}:){1,7}:| - ([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}| - ([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}| - ([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}| - ([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}| - ([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}| - [0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})| - :((:[0-9a-f]{1,4}){1,7}|:)| - fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}| - ::(ffff(:0{1,4}){0,1}:){0,1} - ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| - ([0-9a-f]{1,4}:){1,4}: - ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} - (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix - false + # @overload self.is_valid_ip?(version, ip) + # Checks if the given string is a valid IP address + # @param [Symbol] version IP version (either :ipv4 or :ipv6) + # @param [String] ip string to be tested + # @return [Boolean] true if the string is a valid IP address, otherwise false + # + # @overload self.is_valid_ip?(ip) + # Checks if the given string is either a valid IPv4 or IPv6 address + # @param [String] ip string to be tested + # @return [Boolean] true if the string is a valid IPv4 or IPV6 address, otherwise false + def self.is_valid_ip?(version = :both, ip) + valid = false + + if is_non_empty_string?(ip) + valid = case version.inspect.downcase + when /^:ipv4$/ + ip =~ /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/x + when /^:ipv6$/ + ip =~ /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,7}:| + ([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}| + ([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}| + ([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}| + ([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}| + ([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}| + [0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})| + :((:[0-9a-f]{1,4}){1,7}|:)| + fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}| + ::(ffff(:0{1,4}){0,1}:){0,1} + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| + ([0-9a-f]{1,4}:){1,4}: + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix + when /^:both$/ + is_valid_ip?(:ipv4, ip) || is_valid_ip?(:ipv6, ip) + end ? true : false + end + + valid end # Checks if string is a valid domain name @@ -172,6 +182,6 @@ module Filters return false if str.length > 200 true end - + end end diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 910b6cc07..c66dad2d1 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -38,7 +38,7 @@ module BeEF begin src = if resource == Resolv::DNS::Resource::IN::A - if response.is_a?(String) && BeEF::Filters.is_valid_ipv4?(response) + if response.is_a?(String) && BeEF::Filters.is_valid_ip?(:ipv4, response) sprintf "t.respond!('%s')", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex sprintf "t.fail!(:%s)", response.to_sym @@ -47,7 +47,7 @@ module BeEF str2 = '' response.each do |r| - raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ipv4?(r) + raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(:ipv4, r) str2 << sprintf(str1, r) end @@ -56,7 +56,7 @@ module BeEF raise InvalidDnsResponseError, 'A' end elsif resource == Resolv::DNS::Resource::IN::AAAA - if response.is_a?(String) && BeEF::Filters.is_valid_ipv6(response) + if response.is_a?(String) && BeEF::Filters.is_valid_ip?(:ipv6, response) sprintf "t.respond!('%s')", response elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex sprintf "t.fail!(:%s)", response.to_sym @@ -65,7 +65,7 @@ module BeEF str2 = '' response.each do |r| - raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ipv6(r) + raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(:ipv6, r) str2 << sprintf(str1, r) end @@ -126,7 +126,7 @@ module BeEF str2 = '' response.each do |r| - raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_ipv4?(r) + raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r) str2 << sprintf(str1, r) end @@ -188,7 +188,7 @@ module BeEF end elsif resource == Resolv::DNS::Resource::IN::WKS if response.is_a?(Array) - unless BeEF::Filters.is_valid_ipv4?(resource[0]) && + unless BeEF::Filters.is_valid_ip?(resource[0]) && resource[1].is_a?(Integer) && resource[2].is_a?(Integer) raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String) From 7b229a2a206d8dfdc40a46f2340adfdbcaf210ec Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 24 Apr 2014 14:26:37 -0400 Subject: [PATCH 23/40] Added new #validate_pattern method. Uses BeEF::Filters to ensure that empty, null, and non-printable patterns are tossed out. Added new InvalidDnsPatternError exception class to handle these cases. Renamed #validate_response to #format_callback since the name is more appropriate. --- extensions/dns/model.rb | 355 +++++++++++++++++++++------------------- 1 file changed, 188 insertions(+), 167 deletions(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index c66dad2d1..bd2610a4f 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -20,208 +20,229 @@ module BeEF property :response, Object, :required => true property :callback, Object, :required => true - # Hooks the model's "save" event. Generates a rule identifier and callback. + # Hooks the model's "save" event. Validates pattern/response and generates a rule identifier. before :save do |rule| - rule.callback = validate_response(rule.resource, rule.response) + begin + validate_pattern(rule.pattern) + rule.callback = format_callback(rule.resource, rule.response) + rescue InvalidDnsPatternError, InvalidDnsResourceError, InvalidDnsResponseError => e + print_error e.message + throw :halt + end + rule.id = BeEF::Core::Crypto.dns_rule_id end private + # Verifies that the given pattern is valid (i.e. non-empty, no null's or printable characters). + def validate_pattern(pattern) + raise InvalidDnsPatternError unless BeEF::Filters.is_non_empty_string?(pattern) && + !BeEF::Filters.has_null?(pattern) && + !BeEF::Filters.has_non_printable_char?(pattern) + end + # Strict validator which ensures that only an appropriate response is given. # # @param resource [Resolv::DNS::Resource::IN] resource record type # @param response [String, Symbol, Array] response to include in callback # # @return [String] string representation of callback that can safely be eval'd - def validate_response(resource, response) + def format_callback(resource, response) sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i - begin - src = if resource == Resolv::DNS::Resource::IN::A - if response.is_a?(String) && BeEF::Filters.is_valid_ip?(:ipv4, response) - sprintf "t.respond!('%s')", response - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - elsif response.is_a?(Array) - str1 = "t.respond!('%s');" - str2 = '' + src = if resource == Resolv::DNS::Resource::IN::A + if response.is_a?(String) && BeEF::Filters.is_valid_ip?(:ipv4, response) + sprintf "t.respond!('%s')", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + elsif response.is_a?(Array) + str1 = "t.respond!('%s');" + str2 = '' - response.each do |r| - raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(:ipv4, r) - str2 << sprintf(str1, r) - end - - str2 - else - raise InvalidDnsResponseError, 'A' + response.each do |r| + raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(:ipv4, r) + str2 << sprintf(str1, r) end - elsif resource == Resolv::DNS::Resource::IN::AAAA - if response.is_a?(String) && BeEF::Filters.is_valid_ip?(:ipv6, response) - sprintf "t.respond!('%s')", response - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - elsif response.is_a?(Array) - str1 = "t.respond!('%s');" - str2 = '' - response.each do |r| - raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(:ipv6, r) - str2 << sprintf(str1, r) - end + str2 + else + raise InvalidDnsResponseError, 'A' + end + elsif resource == Resolv::DNS::Resource::IN::AAAA + if response.is_a?(String) && BeEF::Filters.is_valid_ip?(:ipv6, response) + sprintf "t.respond!('%s')", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + elsif response.is_a?(Array) + str1 = "t.respond!('%s');" + str2 = '' - str2 - else - raise InvalidDnsResponseError, 'AAAA' + response.each do |r| + raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(:ipv6, r) + str2 << sprintf(str1, r) end - elsif resource == Resolv::DNS::Resource::IN::CNAME - if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) - sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'CNAME' + + str2 + else + raise InvalidDnsResponseError, 'AAAA' + end + elsif resource == Resolv::DNS::Resource::IN::CNAME + if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) + sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'CNAME' + end + elsif resource == Resolv::DNS::Resource::IN::HINFO + if response.is_a?(Array) + response.each { |r| raise InvalidDnsResponseError, 'HINFO' unless r.is_a?(String) } + data = { :cpu => response[0], :os => response[1] } + sprintf "t.respond!('%s', '%s')", data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'HINFO' + end + elsif resource == Resolv::DNS::Resource::IN::MINFO + if response.is_a?(Array) + response.each { |r| raise InvalidDnsResponseError, 'MINFO' unless r.is_a?(String) && BeEF::Filters.is_valid_domain?(r) } + + data = { :rmailbx => response[0], :emailbx => response[1] } + + sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + + "Resolv::DNS::Name.create('%s'))", + data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'MINFO' + end + elsif resource == Resolv::DNS::Resource::IN::MX + if response[0].is_a?(Integer) && + BeEF::Filters.is_valid_domain?(response[1]) + + data = { :preference => response[0], :exchange => response[1] } + sprintf "t.respond!(%d, Resolv::DNS::Name.create('%s'))", data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'MX' + end + elsif resource == Resolv::DNS::Resource::IN::NS + if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) + sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + elsif response.is_a?(Array) + str1 = "t.respond!(Resolv::DNS::Name.create('%s'))" + str2 = '' + + response.each do |r| + raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r) + str2 << sprintf(str1, r) end - elsif resource == Resolv::DNS::Resource::IN::HINFO - if response.is_a?(Array) - response.each { |r| raise InvalidDnsResponseError, 'HINFO' unless r.is_a?(String) } - data = { :cpu => response[0], :os => response[1] } - sprintf "t.respond!('%s', '%s')", data - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'HINFO' - end - elsif resource == Resolv::DNS::Resource::IN::MINFO - if response.is_a?(Array) - response.each { |r| raise InvalidDnsResponseError, 'MINFO' unless r.is_a?(String) && BeEF::Filters.is_valid_domain?(r) } - data = { :rmailbx => response[0], :emailbx => response[1] } + str2 + else + raise InvalidDnsResponseError, 'NS' + end + elsif resource == Resolv::DNS::Resource::IN::PTR + if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) + sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'PTR' + end + elsif resource == Resolv::DNS::Resource::IN::SOA + if response.is_a?(Array) + unless BeEF::Filters.is_valid_domain?(response[0]) && + BeEF::Filters.is_valid_domain?(response[1]) && + response[2].is_a?(Integer) && + response[3].is_a?(Integer) && + response[4].is_a?(Integer) && + response[5].is_a?(Integer) && + response[6].is_a?(Integer) - sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + - "Resolv::DNS::Name.create('%s'))", - data - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'MINFO' - end - elsif resource == Resolv::DNS::Resource::IN::MX - if response[0].is_a?(Integer) && - BeEF::Filters.is_valid_domain?(response[1]) - - data = { :preference => response[0], :exchange => response[1] } - sprintf "t.respond!(%d, Resolv::DNS::Name.create('%s'))", data - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'MX' - end - elsif resource == Resolv::DNS::Resource::IN::NS - if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) - sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - elsif response.is_a?(Array) - str1 = "t.respond!(Resolv::DNS::Name.create('%s'))" - str2 = '' - - response.each do |r| - raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r) - str2 << sprintf(str1, r) - end - - str2 - else - raise InvalidDnsResponseError, 'NS' - end - elsif resource == Resolv::DNS::Resource::IN::PTR - if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response) - sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'PTR' - end - elsif resource == Resolv::DNS::Resource::IN::SOA - if response.is_a?(Array) - unless BeEF::Filters.is_valid_domain?(response[0]) && - BeEF::Filters.is_valid_domain?(response[1]) && - response[2].is_a?(Integer) && - response[3].is_a?(Integer) && - response[4].is_a?(Integer) && - response[5].is_a?(Integer) && - response[6].is_a?(Integer) - - raise InvalidDnsResponseError, 'SOA' - end - - data = { - :mname => response[0], - :rname => response[1], - :serial => response[2], - :refresh => response[3], - :retry => response[4], - :expire => response[5], - :minimum => response[6] - } - - sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + - "Resolv::DNS::Name.create('%s'), " + - '%d, ' + - '%d, ' + - '%d, ' + - '%d, ' + - '%d)', - data - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else raise InvalidDnsResponseError, 'SOA' end - elsif resource == Resolv::DNS::Resource::IN::TXT - if resource.is_a?(String) - sprintf "t.respond!('%s')", response - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'TXT' - end - elsif resource == Resolv::DNS::Resource::IN::WKS - if response.is_a?(Array) - unless BeEF::Filters.is_valid_ip?(resource[0]) && - resource[1].is_a?(Integer) && - resource[2].is_a?(Integer) - raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String) - end - data = { - :address => response[0], - :protocol => response[1], - :bitmap => response[2] - } + data = { + :mname => response[0], + :rname => response[1], + :serial => response[2], + :refresh => response[3], + :retry => response[4], + :expire => response[5], + :minimum => response[6] + } - sprintf "t.respond!('%
s', %d, %d)", data - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'WKS' - end + sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + + "Resolv::DNS::Name.create('%s'), " + + '%d, ' + + '%d, ' + + '%d, ' + + '%d, ' + + '%d)', + data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym else - raise UnknownDnsResourceError + raise InvalidDnsResponseError, 'SOA' end - rescue InvalidDnsResponseError, UnknownDnsResourceError => e - print_error e.message - throw :halt + elsif resource == Resolv::DNS::Resource::IN::TXT + if resource.is_a?(String) + sprintf "t.respond!('%s')", response + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'TXT' + end + elsif resource == Resolv::DNS::Resource::IN::WKS + if response.is_a?(Array) + unless BeEF::Filters.is_valid_ip?(resource[0]) && + resource[1].is_a?(Integer) && + resource[2].is_a?(Integer) + raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String) + end + + data = { + :address => response[0], + :protocol => response[1], + :bitmap => response[2] + } + + sprintf "t.respond!('%
s', %d, %d)", data + elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex + sprintf "t.fail!(:%s)", response.to_sym + else + raise InvalidDnsResponseError, 'WKS' + end + else + raise UnknownDnsResourceError end src end + # Raised when an invalid pattern is given. + class InvalidDnsPatternError < StandardError + + DEFAULT_MESSAGE = 'Failed to add DNS rule with invalid pattern' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + + end + # Raised when a response is not valid for the given DNS resource record. class InvalidDnsResponseError < StandardError def initialize(message = nil) - message = sprintf "Invalid response specified for %s resource record", message unless message.nil? + str = "Failed to add DNS rule with invalid response for %s resource record", message + message = sprintf str, message unless message.nil? super(message) end @@ -230,7 +251,7 @@ module BeEF # Raised when an unknown DNS resource record is given. class UnknownDnsResourceError < StandardError - DEFAULT_MESSAGE = 'Unknown resource record was given' + DEFAULT_MESSAGE = 'Failed to add DNS rule with unknown resource record' def initialize(message = nil) super(message || DEFAULT_MESSAGE) From 94fc2805d197ebf08adfc6bc648f75eb5a19bb89 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 24 Apr 2014 16:36:44 -0400 Subject: [PATCH 24/40] Added ID filter checks to #get_rule. This is ensures that ID validation occurs when using either the Ruby API or the RESTful API. Previously, validation was only done for the RESTful API. --- extensions/dns/dns.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 43782af8f..d1ed4d4d1 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -63,8 +63,14 @@ module BeEF # @return [Hash] hash representation of rule (empty hash if rule wasn't found) def get_rule(id) @lock.synchronize do - rule = @database.get(id) - rule.nil? ? {} : to_hash(rule) + if BeEF::Filters.hexs_only?(id) && + !BeEF::Filters.has_null?(id) && + !BeEF::Filters.has_non_printable_char?(id) && + id.length == 8 + + rule = @database.get(id) + rule.nil? ? {} : to_hash(rule) + end end end From bca9eccdf03520d738e976d4474a48bb3ffeadd9 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 24 Apr 2014 16:40:19 -0400 Subject: [PATCH 25/40] Implemented GET ruleset, rule, and POST rule handlers. Many filter checks were removed because the new DNS extension performs validation before performing any database operation. Modified message for InvalidParamError to be more modular. --- extensions/dns/api.rb | 3 +- extensions/dns/extension.rb | 3 +- extensions/dns/rest/dns.rb | 75 +++++++++++-------------------------- 3 files changed, 24 insertions(+), 57 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index cac80f2d6..232fc7db1 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -72,8 +72,7 @@ module BeEF # # @param beef_server [BeEF::Core::Server] HTTP server instance def self.mount_handler(beef_server) - # @todo Uncomment when RESTful API is DNS 2.0 compliant. - #beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new) + beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new) end end diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index b7732a576..39c66da6d 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -22,5 +22,4 @@ require 'extensions/dns/api' require 'extensions/dns/dns' require 'extensions/dns/logger' require 'extensions/dns/model' -# @todo Uncomment when RESTful API is DNS 2.0 compliant. -#require 'extensions/dns/rest/dns' +require 'extensions/dns/rest/dns' diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 4413c7ec7..c92592e6e 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -1,5 +1,5 @@ # -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # @@ -45,14 +45,11 @@ module BeEF begin id = params[:id] - unless BeEF::Filters.alphanums_only?(id) - raise InvalidParamError, 'Invalid "id" parameter passed to endpoint /api/dns/rule/:id' - end + rule = BeEF::Extension::Dns::Server.instance.get_rule(id) + raise InvalidParamError, 'id' if rule.nil? + halt 404 if rule.empty? - result = BeEF::Extension::Dns::Server.instance.get_rule(id) - halt 404 if result.length == 0 - - result.to_json + rule.to_json rescue InvalidParamError => e print_error e.message halt 400 @@ -68,58 +65,26 @@ module BeEF body = JSON.parse(request.body.read) pattern = body['pattern'] - type = body['type'] + resource = body['resource'] response = body['response'] - valid_types = ["A", "AAAA", "CNAME", "HINFO", "MINFO", "MX", "NS", "PTR", "SOA", "TXT", "WKS"] + valid_resources = ["A", "AAAA", "CNAME", "HINFO", "MINFO", "MX", "NS", "PTR", "SOA", "TXT", "WKS"] # Validate required JSON keys - unless [pattern, type, response].include?(nil) - # Determine whether 'pattern' is a String or Regexp - begin - # if pattern is a Regexp, then create a new Regexp object - if %r{\A/(.*)/([mix]*)\z} =~ pattern - pattern = Regexp.new(pattern) - end - rescue => e; - end - - if response.class == Array - if response.length == 0 - raise InvalidJsonError, 'Empty "response" key passed to endpoint /api/dns/rule' - end + unless [pattern, resource, response].include?(nil) + if response.is_a?(Array) + raise InvalidJsonError, 'Empty "response" key passed to endpoint /api/dns/rule' if response.empty? else raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule' end - safe_response = true - response.each do |ip| - unless BeEF::Filters.is_valid_ip?(ip) - safe_response = false - break - end - end + raise InvalidJsonError, 'Wrong "resource" key passed to endpoint /api/dns/rule' unless valid_resources.include?(resource) - unless safe_response - raise InvalidJsonError, 'Invalid IP in "response" key passed to endpoint /api/dns/rule' - end - - unless BeEF::Filters.is_non_empty_string?(pattern) - raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule' - end - - unless BeEF::Filters.is_non_empty_string?(type) && BeEF::Filters.alphanums_only?(type) && valid_types.include?(type) - raise InvalidJsonError, 'Wrong "type" key passed to endpoint /api/dns/rule' - end - - id = '' - block_src = format_response(type, response) - - # antisnatchor: would be unsafe eval, but I added 2 validations before (alpha-num only and list of valid types) - # Now it's safe - type_obj = eval "Resolv::DNS::Resource::IN::#{type}" - - id = BeEF::Extension::Dns::Server.instance.get_server.match(pattern, type_obj, block_src) + id = BeEF::Extension::Dns::Server.instance.add_rule( + :pattern => pattern, + :resource => eval("Resolv::DNS::Resource::IN::#{resource}"), + :response => response + ) result = {} result['success'] = true @@ -135,13 +100,14 @@ module BeEF end end +=begin # Removes a rule given its id delete '/rule/:id' do begin id = params[:id] unless BeEF::Filters.alphanums_only?(id) - raise InvalidParamError, 'Invalid "id" parameter passed to endpoint /api/dns/rule/:id' + raise InvalidParamError, 'id' end result = {} @@ -231,6 +197,7 @@ module BeEF sprintf(src, args) end +=end # Raised when invalid JSON input is passed to an /api/dns handler. class InvalidJsonError < StandardError @@ -249,7 +216,9 @@ module BeEF DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler' def initialize(message = nil) - super(message || DEFAULT_MESSAGE) + str = "Invalid \"%s\" parameter passed to /api/dns handler" + message = sprintf str, message unless message.nil? + super(message) end end From 32db367ada81ce152edfb926c0c8a4cbfd6ff7c6 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 24 Apr 2014 23:28:11 -0400 Subject: [PATCH 26/40] Refactored ID filter checks into #is_valid_id?. Added call to #remove_rule! as well. --- extensions/dns/dns.rb | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index d1ed4d4d1..5bdf11202 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -63,11 +63,7 @@ module BeEF # @return [Hash] hash representation of rule (empty hash if rule wasn't found) def get_rule(id) @lock.synchronize do - if BeEF::Filters.hexs_only?(id) && - !BeEF::Filters.has_null?(id) && - !BeEF::Filters.has_non_printable_char?(id) && - id.length == 8 - + if is_valid_id?(id) rule = @database.get(id) rule.nil? ? {} : to_hash(rule) end @@ -81,8 +77,10 @@ module BeEF # @return [Boolean] true if rule was removed, otherwise false def remove_rule!(id) @lock.synchronize do - rule = @database.get(id) - rule.nil? ? false : rule.destroy + if is_valid_id?(id) + rule = @database.get(id) + rule.nil? ? false : rule.destroy + end end end @@ -160,6 +158,18 @@ module BeEF hash end + # Verifies that the given ID is valid. + # + # @param id [String] identifier to validate + # + # @return [Boolean] true if ID is valid, otherwise false + def is_valid_id?(id) + BeEF::Filters.hexs_only?(id) && + !BeEF::Filters.has_null?(id) && + !BeEF::Filters.has_non_printable_char?(id) && + id.length == 8 + end + # Helper method that formats the given resource class in a human-readable format. # # @param resource [Resolv::DNS::Resource::IN] resource class From acc6114541c60fe7300c93fdf94a370ee11ef27d Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 24 Apr 2014 23:35:21 -0400 Subject: [PATCH 27/40] Re-implemented DELETE /rule/:id handler for new API. Removed old #format_response method since this is now handled by the Rule model. --- extensions/dns/rest/dns.rb | 85 ++------------------------------------ 1 file changed, 3 insertions(+), 82 deletions(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index c92592e6e..dbadd1445 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -100,18 +100,16 @@ module BeEF end end -=begin # Removes a rule given its id delete '/rule/:id' do begin id = params[:id] - unless BeEF::Filters.alphanums_only?(id) - raise InvalidParamError, 'id' - end + removed = BeEF::Extension::Dns::Server.instance.remove_rule!(id) + raise InvalidParamError, 'id' if removed.nil? result = {} - result['success'] = BeEF::Extension::Dns::Server.instance.remove_rule(id) + result['success'] = removed result.to_json rescue InvalidParamError => e print_error e.message @@ -122,83 +120,6 @@ module BeEF end end - private - - # Generates a formatted string representation of the callback to invoke as a response. - # - # @param [String] type resource record type (e.g. A, CNAME, NS, etc.) - # @param [Array] rdata record data to include in response - # - # @return [String] string representation of response callback - def format_response(type, rdata) - src = 'proc { |t| t.respond!(%s) }' - - args = case type - when 'A' - data = {:address => rdata[0]} - sprintf "'%
s'", data - when 'AAAA' - data = {:address => rdata[0]} - sprintf "'%
s'", data - when 'CNAME' - data = {:cname => rdata[0]} - sprintf "Resolv::DNS::Name.create('%s')", data - when 'HINFO' - data = {:cpu => rdata[0], :os => rdata[1]} - sprintf "'%s', '%s'", data - when 'MINFO' - data = {:rmailbx => rdata[0], :emailbx => rdata[1]} - - sprintf "Resolv::DNS::Name.create('%s'), " + - "Resolv::DNS::Name.create('%s')", - data - when 'MX' - data = {:preference => rdata[0], :exchange => rdata[1]} - sprintf "%d, Resolv::DNS::Name.create('%s')", data - when 'NS' - data = {:nsdname => rdata[0]} - sprintf "Resolv::DNS::Name.create('%s')", data - when 'PTR' - data = {:ptrdname => rdata[0]} - sprintf "Resolv::DNS::Name.create('%s')", data - when 'SOA' - data = { - :mname => rdata[0], - :rname => rdata[1], - :serial => rdata[2], - :refresh => rdata[3], - :retry => rdata[4], - :expire => rdata[5], - :minimum => rdata[6] - } - - sprintf "Resolv::DNS::Name.create('%s'), " + - "Resolv::DNS::Name.create('%s'), " + - '%d, ' + - '%d, ' + - '%d, ' + - '%d, ' + - '%d', - data - when 'TXT' - data = {:txtdata => rdata[0]} - sprintf "'%s'", data - when 'WKS' - data = { - :address => rdata[0], - :protocol => rdata[1], - :bitmap => rdata[2] - } - - sprintf "'%
s', %d, %d", data - else - raise InvalidJsonError, 'Unknown "type" key passed to endpoint /api/dns/rule' - end - - sprintf(src, args) - end -=end - # Raised when invalid JSON input is passed to an /api/dns handler. class InvalidJsonError < StandardError From 2e318030daf9678d85f59a37db2483f8a13dd717 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 25 Apr 2014 10:09:38 -0400 Subject: [PATCH 28/40] Fixed typo in debug message for #process. --- extensions/dns/dns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 5bdf11202..2c7467aaa 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -113,7 +113,7 @@ module BeEF # @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer def process(name, resource, transaction) @lock.synchronize do - print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)}" + print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})" catch (:done) do # Find rules matching the requested resource class From 1d5afbb81ec65c86510b837d657d23378cca6c18 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 25 Apr 2014 10:14:05 -0400 Subject: [PATCH 29/40] Added Dns::Server attribute to DNS REST router. This is primarily intended to add clarity by reducing clutter. Moreover, it also has the side effect of improving performance very slightly by removing the overhead of calling #instance numerous times. --- extensions/dns/rest/dns.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index dbadd1445..a1cdca5bf 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -12,6 +12,7 @@ module BeEF # Filters out bad requests before performing any routing before do + @dns ||= BeEF::Extension::Dns::Server.instance config = BeEF::Core::Configuration.instance # Require a valid API token from a valid IP address @@ -27,7 +28,7 @@ module BeEF # Returns the entire current DNS ruleset get '/ruleset' do begin - ruleset = BeEF::Extension::Dns::Server.instance.get_ruleset + ruleset = @dns.get_ruleset count = ruleset.length result = {} @@ -45,7 +46,7 @@ module BeEF begin id = params[:id] - rule = BeEF::Extension::Dns::Server.instance.get_rule(id) + rule = @dns.get_rule(id) raise InvalidParamError, 'id' if rule.nil? halt 404 if rule.empty? @@ -80,7 +81,7 @@ module BeEF raise InvalidJsonError, 'Wrong "resource" key passed to endpoint /api/dns/rule' unless valid_resources.include?(resource) - id = BeEF::Extension::Dns::Server.instance.add_rule( + id = @dns.add_rule( :pattern => pattern, :resource => eval("Resolv::DNS::Resource::IN::#{resource}"), :response => response @@ -105,7 +106,7 @@ module BeEF begin id = params[:id] - removed = BeEF::Extension::Dns::Server.instance.remove_rule!(id) + removed = @dns.remove_rule!(id) raise InvalidParamError, 'id' if removed.nil? result = {} From 82e4b1eac794df56bb4d5de3340b5fb8a8856a7c Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 25 Apr 2014 10:32:19 -0400 Subject: [PATCH 30/40] Implemented default DNS address/port/protocol values. Even though it is unlikely that a user would remove these options from the DNS config file, it is still good practice to have these safeguards in place. --- extensions/dns/api.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 232fc7db1..cbac81b6c 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -29,9 +29,9 @@ module BeEF dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns') dns = BeEF::Extension::Dns::Server.instance - protocol = dns_config['protocol'].to_sym - address = dns_config['address'] - port = dns_config['port'] + protocol = dns_config['protocol'].to_sym rescue :udp + address = dns_config['address'] || '127.0.0.1' + port = dns_config['port'] || 5300 interfaces = [[protocol, address, port]] Thread.new { EventMachine.next_tick { dns.run(:listen => interfaces) } } From 9b3dfacce182200a6dc4d620d6b11caf907422fe Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 25 Apr 2014 13:06:33 -0400 Subject: [PATCH 31/40] Added support for upstream nameservers. Previously, upstream nameservers were configured by default even if the config file did not specify them. Now upstream nameservers are only used if they are specified. If none are given, then NXDOMAIN is returned for unresolvable requests. --- extensions/dns/api.rb | 33 ++++++++++----------------------- extensions/dns/config.yaml | 4 ++-- extensions/dns/dns.rb | 24 ++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index cbac81b6c..ca0accd45 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -34,38 +34,25 @@ module BeEF port = dns_config['port'] || 5300 interfaces = [[protocol, address, port]] - Thread.new { EventMachine.next_tick { dns.run(:listen => interfaces) } } - - print_info "DNS Server: #{address}:#{port} (#{protocol})" - - # @todo Upstream servers are not yet supported. Uncomment this section when they are. -=begin servers = [] + upstream_servers = '' - unless dns_config['upstream'].nil? + unless dns_config['upstream'].nil? || dns_config['upstream'].empty? dns_config['upstream'].each do |server| - next if server[1].nil? or server[2].nil? + up_protocol = server[0].downcase + up_address = server[1] + up_port = server[2] - if server[0] == 'tcp' - servers << ['tcp', server[1], server[2]] - elsif server[0] == 'udp' - servers << ['udp', server[1], server[2]] - end + next if [up_protocol, up_address, up_port].include?(nil) + servers << [up_protocol.to_sym, up_address, up_port] if up_protocol =~ /^(tcp|udp)$/ + upstream_servers << "Upstream Server: #{up_address}:#{up_port} (#{up_port})\n" end end - if servers.empty? - servers << ['tcp', '8.8.8.8', 53] - servers << ['udp', '8.8.8.8', 53] - end - - upstream_servers = '' - servers.each do |server| - upstream_servers << "Upstream Server: #{server[1]}:#{server[2]} (#{server[0]})\n" - end + Thread.new { EventMachine.next_tick { dns.run(:upstream => servers, :listen => interfaces) } } + print_info "DNS Server: #{address}:#{port} (#{protocol})" print_more upstream_servers -=end end # Mounts the handler for processing DNS RESTful API requests. diff --git a/extensions/dns/config.yaml b/extensions/dns/config.yaml index 3733af7ea..5a525a2e3 100644 --- a/extensions/dns/config.yaml +++ b/extensions/dns/config.yaml @@ -13,6 +13,6 @@ beef: address: '127.0.0.1' port: 5300 upstream: [ - ['tcp', '8.8.8.8', 53], - ['udp', '8.8.8.8', 53] + ['udp', '8.8.8.8', 53], + ['tcp', '8.8.8.8', 53] ] diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 2c7467aaa..e01271cdd 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -105,6 +105,26 @@ module BeEF @lock.synchronize { @database.destroy } end + # Starts the DNS server. + # + # @param options [Hash] server configuration options + # @option options [Array] :upstream upstream DNS servers (if ommitted, unresolvable + # requests return NXDOMAIN) + # @option options [Array] :listen local interfaces to listen on + def run(options = {}) + @lock.synchronize do + upstream = options[:upstream] + listen = options[:listen] + + unless upstream.nil? || upstream.empty? + resolver = RubyDNS::Resolver.new(upstream) + @otherwise = Proc.new { |t| t.passthrough!(resolver) } + end + + super(:listen => listen) + end + end + # Entry point for processing incoming DNS requests. Attempts to find a matching rule and # sends back its associated response. # @@ -131,12 +151,12 @@ module BeEF end end - # When no match is found, query upstream servers (if enabled) if @otherwise print_debug "No match found, querying upstream servers" @otherwise.call(transaction) else - print_debug "Failed to handle DNS request for #{name}" + print_debug "No match found, sending NXDOMAIN response" + transaction.fail!(:NXDomain) end end end From 3b3d7fe95e6964f9203d4af937a1ad320692cad0 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 25 Apr 2014 13:14:43 -0400 Subject: [PATCH 32/40] Moved Thread/EventMachine creation inside of #run. This cleans up the API a bit by removing the requirement of placing #run inside a Thread.new {EventMachine.next_tick {}} block. That should not be the caller's responsibility. --- extensions/dns/api.rb | 2 +- extensions/dns/dns.rb | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index ca0accd45..60c9d3927 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -49,7 +49,7 @@ module BeEF end end - Thread.new { EventMachine.next_tick { dns.run(:upstream => servers, :listen => interfaces) } } + dns.run(:upstream => servers, :listen => interfaces) print_info "DNS Server: #{address}:#{port} (#{protocol})" print_more upstream_servers diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index e01271cdd..177414c5d 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -113,15 +113,19 @@ module BeEF # @option options [Array] :listen local interfaces to listen on def run(options = {}) @lock.synchronize do - upstream = options[:upstream] - listen = options[:listen] + Thread.new do + EventMachine.next_tick do + upstream = options[:upstream] || nil + listen = options[:listen] || nil - unless upstream.nil? || upstream.empty? - resolver = RubyDNS::Resolver.new(upstream) - @otherwise = Proc.new { |t| t.passthrough!(resolver) } + if upstream + resolver = RubyDNS::Resolver.new(upstream) + @otherwise = Proc.new { |t| t.passthrough!(resolver) } + end + + super(:listen => listen) + end end - - super(:listen => listen) end end From 0438cf422f1900c4d01e6efd8f8fcf61d8735059 Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Sat, 26 Apr 2014 21:50:46 +1000 Subject: [PATCH 33/40] use up_protocol in banner --- extensions/dns/api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 60c9d3927..138541717 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -45,7 +45,7 @@ module BeEF next if [up_protocol, up_address, up_port].include?(nil) servers << [up_protocol.to_sym, up_address, up_port] if up_protocol =~ /^(tcp|udp)$/ - upstream_servers << "Upstream Server: #{up_address}:#{up_port} (#{up_port})\n" + upstream_servers << "Upstream Server: #{up_address}:#{up_port} (#{up_protocol})\n" end end From e6b74d51860c79e6943090da7d879295b2fc31ab Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Sun, 27 Apr 2014 00:14:25 +1000 Subject: [PATCH 34/40] Update model.rb - Throw 'UnknownDnsResourceError' Throw `UnknownDnsResourceError` instead of `InvalidDnsResourceError` Prevents `[20:30:55][!] Internal error while adding DNS rule (uninitialized constant BeEF::Core::Models::Dns::Rule::InvalidDnsResourceError)` for invalid user supplied DNS response types. 'BeEF::Core::Models::Dns::Rule::InvalidDnsResourceError' does not exist, and it's unlikely we'll need to differentiate between invalid and unknown resource types. --- extensions/dns/model.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index bd2610a4f..d12c2fb15 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -25,7 +25,7 @@ module BeEF begin validate_pattern(rule.pattern) rule.callback = format_callback(rule.resource, rule.response) - rescue InvalidDnsPatternError, InvalidDnsResourceError, InvalidDnsResponseError => e + rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e print_error e.message throw :halt end From c63a55962a84b36384c841d415df4a6411fc30f8 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 28 Apr 2014 20:20:32 -0400 Subject: [PATCH 35/40] Added unless modifier to prevent displaying no upstream servers. Even though #print_more will display nothing since ''.split("\n").each() iterates 0 times, it will still be called without this modifier which is unnecessary. --- extensions/dns/api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 138541717..974b30488 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -52,7 +52,7 @@ module BeEF dns.run(:upstream => servers, :listen => interfaces) print_info "DNS Server: #{address}:#{port} (#{protocol})" - print_more upstream_servers + print_more upstream_servers unless upstream_servers.empty? end # Mounts the handler for processing DNS RESTful API requests. From 26cd0f08ad16504b47447c04490b8ed84cd90a58 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 28 Apr 2014 20:28:47 -0400 Subject: [PATCH 36/40] Removed support for HINFO and MINFO resource records. These RR's are very difficult to validate and, in their current state, are vulnerable to RCE attacks. Furthermore, BeEF does not have a use for these RR's. --- extensions/dns/model.rb | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index d12c2fb15..210914fde 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -94,30 +94,6 @@ module BeEF else raise InvalidDnsResponseError, 'CNAME' end - elsif resource == Resolv::DNS::Resource::IN::HINFO - if response.is_a?(Array) - response.each { |r| raise InvalidDnsResponseError, 'HINFO' unless r.is_a?(String) } - data = { :cpu => response[0], :os => response[1] } - sprintf "t.respond!('%s', '%s')", data - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'HINFO' - end - elsif resource == Resolv::DNS::Resource::IN::MINFO - if response.is_a?(Array) - response.each { |r| raise InvalidDnsResponseError, 'MINFO' unless r.is_a?(String) && BeEF::Filters.is_valid_domain?(r) } - - data = { :rmailbx => response[0], :emailbx => response[1] } - - sprintf "t.respond!(Resolv::DNS::Name.create('%s'), " + - "Resolv::DNS::Name.create('%s'))", - data - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'MINFO' - end elsif resource == Resolv::DNS::Resource::IN::MX if response[0].is_a?(Integer) && BeEF::Filters.is_valid_domain?(response[1]) From e1c27f4feb4dad776fff27c41c321b6664687cf9 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 28 Apr 2014 20:34:56 -0400 Subject: [PATCH 37/40] Removed support for TXT resource record. Like the HINFO and MINFO RR's, TXT is vulnerable to RCE attacks and has no purpose at the moment. TXT may be needed in the future (e.g. data exfiltration) which is why it has been removed separately. --- extensions/dns/model.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 210914fde..8a7220564 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -167,14 +167,6 @@ module BeEF else raise InvalidDnsResponseError, 'SOA' end - elsif resource == Resolv::DNS::Resource::IN::TXT - if resource.is_a?(String) - sprintf "t.respond!('%s')", response - elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex - sprintf "t.fail!(:%s)", response.to_sym - else - raise InvalidDnsResponseError, 'TXT' - end elsif resource == Resolv::DNS::Resource::IN::WKS if response.is_a?(Array) unless BeEF::Filters.is_valid_ip?(resource[0]) && From 8dac5c95eb546faec55773b40ed1213ae93b67b1 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 28 Apr 2014 22:21:39 -0400 Subject: [PATCH 38/40] Fixed #is_valid_domain? regex to include appended dot. --- core/filters/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/filters/base.rb b/core/filters/base.rb index 3ad37f771..33c5a5b73 100644 --- a/core/filters/base.rb +++ b/core/filters/base.rb @@ -149,7 +149,7 @@ module Filters # TLD's is not static. def self.is_valid_domain?(domain) return false unless is_non_empty_string?(domain) - return true if domain =~ /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,})$/i + return true if domain =~ /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,}).?$/i false end From 6bf0f9d648bbfbf9cb82ad9418842e97571038d1 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 2 May 2014 22:21:56 -0400 Subject: [PATCH 39/40] Updated DNS spoofer in social engineering extension. --- .../web_cloner/web_cloner.rb | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/extensions/social_engineering/web_cloner/web_cloner.rb b/extensions/social_engineering/web_cloner/web_cloner.rb index d82f9645e..2185d682b 100644 --- a/extensions/social_engineering/web_cloner/web_cloner.rb +++ b/extensions/social_engineering/web_cloner/web_cloner.rb @@ -111,21 +111,33 @@ module BeEF interceptor.set :cloned_page, get_page_content(file_path) interceptor.set :db_entry, persist_page(url, mount) - @http_server.mount("#{mount}", interceptor.new) - print_info "Mounting cloned page on URL [#{mount}]" - @http_server.remap - # Add a DNS record spoofing the address of the cloned webpage as the BeEF server if dns_spoof dns = BeEF::Extension::Dns::Server.instance - ip = Socket.ip_address_list.detect { |i| !(i.ipv4_loopback? || i.ipv6_loopback?) } + ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address + ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address + ipv6.gsub!(/%\w*$/, '') domain = url.gsub(%r{^http://}, '') - id = dns.add_rule(domain, Resolv::DNS::Resource::IN::A) do |transaction| - transaction.respond!(ip.ip_address) - end + dns.add_rule( + :pattern => domain, + :resource => Resolv::DNS::Resource::IN::A, + :response => ipv4 + ) unless ipv4.nil? + + dns.add_rule( + :pattern => domain, + :resource => Resolv::DNS::Resource::IN::AAAA, + :response => ipv6 + ) unless ipv6.nil? + + print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]" end + print_info "Mounting cloned page on URL [#{mount}]" + @http_server.mount("#{mount}", interceptor.new) + @http_server.remap + success = true else print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'." From 07f1594a7aca2c116b5b7fb4fccabe098da0cdb7 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sat, 3 May 2014 20:42:40 -0400 Subject: [PATCH 40/40] Removed old DNS RESTful API temporary test suite. Previously, this was used to make writing tests easier without having to run the entire integration test suite (of which it is still a part of). Somehow it accidentally got committed. --- test/integration/ts_dns_rest.rb | 20 -------------------- test/integration/ts_integration.rb | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 test/integration/ts_dns_rest.rb diff --git a/test/integration/ts_dns_rest.rb b/test/integration/ts_dns_rest.rb deleted file mode 100644 index 301d30bba..000000000 --- a/test/integration/ts_dns_rest.rb +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net -# Browser Exploitation Framework (BeEF) - http://beefproject.com -# See the file 'doc/COPYING' for copying permission -# -require '../common/ts_common' -require './tc_dns_rest' - -class TS_DnsIntegrationTests - - def self.suite - suite = Test::Unit::TestSuite.new(name="BeEF DNS Integration Test Suite") - suite << TC_DnsRest.suite - - return suite - end - -end - -Test::Unit::UI::Console::TestRunner.run(TS_DnsIntegrationTests) diff --git a/test/integration/ts_integration.rb b/test/integration/ts_integration.rb index 8eea8a49f..6da122199 100644 --- a/test/integration/ts_integration.rb +++ b/test/integration/ts_integration.rb @@ -16,7 +16,7 @@ require './check_environment' # Basic log in and log out tests require './tc_debug_modules' # RESTful API tests (as well as debug modules) require './tc_login' # Basic log in and log out tests require './tc_jools' # Basic tests for jools - #require './tc_dns_rest' # Basic tests for DNS RESTful API interface +#require './tc_dns_rest' # Basic tests for DNS RESTful API interface require './tc_social_engineering_rest' # Basic tests for social engineering RESTful API interface class TS_BeefIntegrationTests