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
diff --git a/config.yaml b/config.yaml
index ea678242d..05dbb3982 100644
--- a/config.yaml
+++ b/config.yaml
@@ -146,4 +146,4 @@ beef:
enable: true
# this is still experimental, we're working on it..
dns:
- enable: false
+ enable: true
diff --git a/core/filters/base.rb b/core/filters/base.rb
index 0c96fde20..33c5a5b73 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,14 +98,58 @@ module Filters
return false if not is_non_empty_string?(str)
only?("a-zA-Z0-9", str)
end
-
- # Check if valid ip address string
- # @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})?$/
+
+ # @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
+ # @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
@@ -138,6 +182,6 @@ module Filters
return false if str.length > 200
true
end
-
+
end
end
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'
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/api.rb b/extensions/dns/api.rb
index 5070a3ac2..974b30488 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,53 +11,53 @@ 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')
-
- address = dns_config['address']
- port = dns_config['port']
-
dns = BeEF::Extension::Dns::Server.instance
- dns.run_server(address, port)
- print_info "DNS Server: #{address}:#{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]]
+
servers = []
- unless dns_config['upstream'].nil?
+ upstream_servers = ''
+
+ unless dns_config['upstream'].nil? || dns_config['upstream'].empty?
dns_config['upstream'].each do |server|
- if server[1].nil? or server[2].nil?
- next
- end
- if server[0] == 'tcp'
- servers << ['tcp', server[1], server[2]]
- elsif server[0] == 'udp'
- servers << ['udp', server[1], server[2]]
- end
+ up_protocol = server[0].downcase
+ up_address = server[1]
+ up_port = server[2]
+
+ 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_protocol})\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
- print_more upstream_servers
+
+ dns.run(:upstream => servers, :listen => interfaces)
+
+ print_info "DNS Server: #{address}:#{port} (#{protocol})"
+ print_more upstream_servers unless upstream_servers.empty?
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)
end
diff --git a/extensions/dns/config.yaml b/extensions/dns/config.yaml
index d382ac70f..5a525a2e3 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: [
+ ['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 6803de477..177414c5d 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,97 +7,81 @@ 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
+ @database = BeEF::Core::Models::Dns::Rule
end
- def set_server(server)
- @server = server
- end
-
- def get_server
- @server
- end
-
- # Starts the main DNS server run-loop in a new thread.
+ # Adds a new DNS rule. If the rule already exists, its current ID is returned.
#
- # @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
+ # @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('foobar.com', Resolv::DNS::Resource::IN::A) do |transaction|
- # transaction.respond!('1.2.3.4')
- # end
+ # id = dns.add_rule(
+ # :pattern => 'browserhacker.com',
+ # :resource => Resolv::DNS::Resource::IN::A,
+ # :response => '1.2.3.4'
+ # )
#
- # @param pattern [String, Regexp] query pattern to recognize
- # @param type [Resolv::DNS::Resource::IN] resource record type (e.g. A, CNAME, NS, etc.)
+ # @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
#
- # @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) }
+ # @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
+
+ @database.first_or_create(
+ { :resource => rule[:resource], :pattern => pattern.source },
+ { :response => rule[:response] }
+ ).id
+ end
end
- # Removes the given DNS rule. Any future queries for it will be passed through.
+ # Retrieves a specific rule given its identifier.
#
- # @param id [Integer] id returned from {#add_rule}
+ # @param id [String] unique identifier for 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
+ # @return [Hash] hash representation of rule (empty hash if rule wasn't found)
def get_rule(id)
- @lock.synchronize { @server.get_rule(id) }
+ @lock.synchronize do
+ if is_valid_id?(id)
+ rule = @database.get(id)
+ rule.nil? ? {} : to_hash(rule)
+ end
+ 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
+ if is_valid_id?(id)
+ rule = @database.get(id)
+ rule.nil? ? false : rule.destroy
+ end
+ end
end
# Returns an AoH representing the entire current DNS ruleset.
@@ -106,60 +90,119 @@ module BeEF
#
# * :id
# * :pattern
- # * :type
+ # * :resource
# * :response
#
- # @return [Array] DNS ruleset (empty if no rules are currently loaded)
+ # @return [Array] DNS ruleset (empty array if no rules are currently defined)
def get_ruleset
- @lock.synchronize { @server.get_ruleset }
+ @lock.synchronize { @database.collect { |rule| to_hash(rule) } }
end
- # Clears the entire DNS ruleset.
+ # Removes 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 }
+ # @return [Boolean] true if ruleset was destroyed, otherwise false
+ def remove_ruleset!
+ @lock.synchronize { @database.destroy }
end
- private
-
- # Common code needed by {#run_server} to start DNS server.
+ # Starts the 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."
+ # @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
+ Thread.new do
+ EventMachine.next_tick do
+ upstream = options[:upstream] || nil
+ listen = options[:listen] || nil
+
+ if upstream
+ resolver = RubyDNS::Resolver.new(upstream)
+ @otherwise = Proc.new { |t| t.passthrough!(resolver) }
end
+
+ super(:listen => listen)
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
+ end
+
+ # Entry point for processing incoming DNS requests. Attempts to find a matching rule and
+ # sends back its associated response.
+ #
+ # @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
+ print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
+
+ catch (:done) do
+ # Find rules matching the requested resource class
+ resources = @database.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
+
+ if @otherwise
+ print_debug "No match found, querying upstream servers"
+ @otherwise.call(transaction)
+ else
+ print_debug "No match found, sending NXDOMAIN response"
+ transaction.fail!(:NXDomain)
+ end
end
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] = format_resource(rule.resource)
+ hash[:response] = rule.response
+
+ 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
+ #
+ # @return [String] resource name stripped of any module/class names
+ def format_resource(resource)
+ /::(\w+)$/.match(resource.name)[1]
+ end
+
end
end
diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb
index fd909f01b..39c66da6d 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,8 @@ 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/rest/dns'
-#require 'extensions/dns/ruby'
+require 'extensions/dns/api'
+require 'extensions/dns/dns'
+require 'extensions/dns/logger'
+require 'extensions/dns/model'
+require 'extensions/dns/rest/dns'
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/model.rb b/extensions/dns/model.rb
index 14f5a2310..8a7220564 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,224 @@ 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, Object, :required => true
+
+ # Hooks the model's "save" event. Validates pattern/response and generates a rule identifier.
+ before :save do |rule|
+ begin
+ validate_pattern(rule.pattern)
+ rule.callback = format_callback(rule.resource, rule.response)
+ rescue InvalidDnsPatternError, UnknownDnsResourceError, 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 format_callback(resource, response)
+ sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i
+
+ 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'
+ 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, '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::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::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)
+ str = "Failed to add DNS rule with invalid response for %s resource record", message
+ message = sprintf str, message unless message.nil?
+ super(message)
+ end
+
+ end
+
+ # Raised when an unknown DNS resource record is given.
+ class UnknownDnsResourceError < StandardError
+
+ DEFAULT_MESSAGE = 'Failed to add DNS rule with unknown resource record'
+
+ def initialize(message = nil)
+ super(message || DEFAULT_MESSAGE)
+ end
+
+ end
end
diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb
index 4413c7ec7..a1cdca5bf 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
#
@@ -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,14 +46,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 = @dns.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 +66,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 = @dns.add_rule(
+ :pattern => pattern,
+ :resource => eval("Resolv::DNS::Resource::IN::#{resource}"),
+ :response => response
+ )
result = {}
result['success'] = true
@@ -140,12 +106,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
+ removed = @dns.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
@@ -156,82 +121,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
-
# Raised when invalid JSON input is passed to an /api/dns handler.
class InvalidJsonError < StandardError
@@ -249,7 +138,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
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
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'."
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
diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb
index bfb7ca2c3..47e12b2d2 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,33 +39,24 @@ 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
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
- 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