From 9f7d326f6f20763b36b3eb2e5d51ce410213625d Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 3 May 2013 17:34:41 -0400 Subject: [PATCH 01/78] Added RubyDNS to Gemfile and core/loader.rb. --- Gemfile | 1 + core/loader.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 96fc0dfc8..16b8678b0 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,7 @@ gem "parseconfig" gem "erubis" gem "dm-migrations" gem "msfrpc-client" +gem "rubydns" # notifications gem "twitter" diff --git a/core/loader.rb b/core/loader.rb index 502a664e5..01e284326 100644 --- a/core/loader.rb +++ b/core/loader.rb @@ -15,6 +15,7 @@ require 'ipaddr' require 'base64' require 'xmlrpc/client' require 'openssl' +require 'rubydns' # @note Include the filters require 'core/filters' @@ -29,4 +30,4 @@ require 'core/api' require 'core/settings' # @note Include the core of BeEF -require 'core/core' \ No newline at end of file +require 'core/core' From cc4b34ed8db19e743142ae701c62d489a2891f7b Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 3 May 2013 21:25:53 -0400 Subject: [PATCH 02/78] Started basic DNS extension. Currently does nothing. --- config.yaml | 2 ++ extensions/dns/config.yaml | 13 +++++++++++++ extensions/dns/extension.rb | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 extensions/dns/config.yaml create mode 100644 extensions/dns/extension.rb diff --git a/config.yaml b/config.yaml index 4e6440c09..8f266117c 100644 --- a/config.yaml +++ b/config.yaml @@ -118,3 +118,5 @@ beef: enable: false ipec: enable: true + dns: + enable: true diff --git a/extensions/dns/config.yaml b/extensions/dns/config.yaml new file mode 100644 index 000000000..91590d06b --- /dev/null +++ b/extensions/dns/config.yaml @@ -0,0 +1,13 @@ +# +# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +beef: + extension: + dns: + enable: true + name: 'DNS Server' + authors: ['soh_cah_toa'] + address: '127.0.0.1' + port: 5300 diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb new file mode 100644 index 000000000..fc0e5d0e8 --- /dev/null +++ b/extensions/dns/extension.rb @@ -0,0 +1,19 @@ +# +# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +module BeEF +module Extension +module DNS + + 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' + +end +end +end From fdd1048f1a5db7e97891a74e57bdbcad4e9244c4 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 3 May 2013 22:37:42 -0400 Subject: [PATCH 03/78] Implemented basic nameserver and configured it to run on BeEF startup. It's worth noting that RubyDNS currently displays a lot of messy output. This needs to be addressed before moving any further. --- extensions/dns/api.rb | 33 +++++++++++++++++++++++++++++++++ extensions/dns/dns.rb | 33 +++++++++++++++++++++++++++++++++ extensions/dns/extension.rb | 3 +++ 3 files changed, 69 insertions(+) create mode 100644 extensions/dns/api.rb create mode 100644 extensions/dns/dns.rb diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb new file mode 100644 index 000000000..a5c838040 --- /dev/null +++ b/extensions/dns/api.rb @@ -0,0 +1,33 @@ +# +# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +module BeEF +module Extension +module DNS +module API + + module NameserverHandler + + BeEF::API::Registrar.instance.register(BeEF::Extension::DNS::API::NameserverHandler, + BeEF::API::Server, + 'pre_http_start') + + def self.pre_http_start(http_hook_server) + config = BeEF::Core::Configuration.instance + + address = config.get('beef.extension.dns.address') + port = config.get('beef.extension.dns.port') + + Thread.new { BeEF::Extension::DNS::DNS.new(address, port) } + + print_info "DNS server: #{address}:#{port}" + end + + end + +end +end +end +end diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb new file mode 100644 index 000000000..d07a19a02 --- /dev/null +++ b/extensions/dns/dns.rb @@ -0,0 +1,33 @@ +# +# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +module BeEF +module Extension +module DNS + + class DNS + + UPSTREAM = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) + + def initialize(address, port) + @address = address + @port = port + + run_server + end + + def run_server + RubyDNS::run_server(:listen => [[:udp, @address, @port]]) do + otherwise do |transaction| + transaction.passthrough!(UPSTREAM) + end + end + end + + end + +end +end +end diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index fc0e5d0e8..04a655c4a 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -17,3 +17,6 @@ module DNS end end end + +require 'extensions/dns/api' +require 'extensions/dns/dns' From d22373d8287543f8c5ee46ce32fb0667c74e1342 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 5 May 2013 21:14:30 -0400 Subject: [PATCH 04/78] Fixed thread issue that occasionally caused BeEF to stop immediately. While using sleep() to fix thread complications is never a great solution, it gets the job done for now. --- extensions/dns/api.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index a5c838040..8d343386c 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -20,9 +20,12 @@ module API address = config.get('beef.extension.dns.address') port = config.get('beef.extension.dns.port') - Thread.new { BeEF::Extension::DNS::DNS.new(address, port) } + Thread.new do + sleep 2 + BeEF::Extension::DNS::DNS.new(address, port) + end - print_info "DNS server: #{address}:#{port}" + print_info "DNS Server: #{address}:#{port}" end end From cbd815c519f10058fa7481cd4baba4daa2216eec Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 5 May 2013 22:19:54 -0400 Subject: [PATCH 05/78] Changed output format for RubyDNS to be "BeEF-compliant". RubyDNS's logger now uses BeEF's print-related functions. Debug messages regarding queries can be enabled using --verbose. --- extensions/dns/extension.rb | 1 + extensions/dns/ruby.rb | 7 +++++++ extensions/dns/ruby/logger.rb | 23 +++++++++++++++++++++++ extensions/dns/ruby/rubydns.rb | 31 +++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 extensions/dns/ruby.rb create mode 100644 extensions/dns/ruby/logger.rb create mode 100644 extensions/dns/ruby/rubydns.rb diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index 04a655c4a..6b1375d2f 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -20,3 +20,4 @@ end require 'extensions/dns/api' require 'extensions/dns/dns' +require 'extensions/dns/ruby' diff --git a/extensions/dns/ruby.rb b/extensions/dns/ruby.rb new file mode 100644 index 000000000..99cf46225 --- /dev/null +++ b/extensions/dns/ruby.rb @@ -0,0 +1,7 @@ +# +# 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 new file mode 100644 index 000000000..46573c1af --- /dev/null +++ b/extensions/dns/ruby/logger.rb @@ -0,0 +1,23 @@ +# +# 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 + +end + diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb new file mode 100644 index 000000000..f407b6f7e --- /dev/null +++ b/extensions/dns/ruby/rubydns.rb @@ -0,0 +1,31 @@ +# +# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +module RubyDNS + + # Overrides RubyDNS::run_server() to behave exactly the same, minus the output + def self.run_server(options = {}, &block) + server = RubyDNS::Server.new(&block) + + 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.fire(:start) + end + + server.fire(:stop) + end + +end From ceb55ef3df529b64936f08c5aaceda88a31d4975 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 6 May 2013 13:09:44 -0400 Subject: [PATCH 06/78] Resolved DNS thread issue using EM::next_tick() instead of sleep(). --- extensions/dns/api.rb | 5 +---- extensions/dns/dns.rb | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 8d343386c..e6e79eee6 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -20,10 +20,7 @@ module API address = config.get('beef.extension.dns.address') port = config.get('beef.extension.dns.port') - Thread.new do - sleep 2 - BeEF::Extension::DNS::DNS.new(address, port) - end + Thread.new { BeEF::Extension::DNS::DNS.new(address, port) } print_info "DNS Server: #{address}:#{port}" end diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index d07a19a02..0b47c5f69 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -15,7 +15,7 @@ module DNS @address = address @port = port - run_server + EventMachine::next_tick { run_server } end def run_server From 493ed5182ba64c1ebe8e542ecab604bf8bd6dbda Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 7 May 2013 21:56:11 -0400 Subject: [PATCH 07/78] Made BeEF::Extension::DNS::DNS into a singleton object. This ensures that all modules/extensions that add new RR's refer to a single server instance. --- extensions/dns/api.rb | 5 ++++- extensions/dns/dns.rb | 22 +++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index e6e79eee6..1161a7ae8 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -20,7 +20,10 @@ module API address = config.get('beef.extension.dns.address') port = config.get('beef.extension.dns.port') - Thread.new { BeEF::Extension::DNS::DNS.new(address, port) } + Thread.new do + dns = BeEF::Extension::DNS::DNS.instance + dns.run_server(address, port) + end print_info "DNS Server: #{address}:#{port}" end diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 0b47c5f69..c13270b7d 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -9,19 +9,23 @@ module DNS class DNS + include Singleton + UPSTREAM = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) - def initialize(address, port) - @address = address - @port = port + #def initialize(address, port) + #@address = address + #@port = port - EventMachine::next_tick { run_server } - end + #EventMachine::next_tick { run_server } + #end - def run_server - RubyDNS::run_server(:listen => [[:udp, @address, @port]]) do - otherwise do |transaction| - transaction.passthrough!(UPSTREAM) + def run_server(address, port) + EventMachine::next_tick do + RubyDNS::run_server(:listen => [[:udp, address, port]]) do + otherwise do |transaction| + transaction.passthrough!(UPSTREAM) + end end end end From 281cde1cbb2076063c19ad33a00ee962982e3859 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 7 May 2013 22:06:13 -0400 Subject: [PATCH 08/78] Added new definition for Logger#warn. --- extensions/dns/ruby/logger.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/dns/ruby/logger.rb b/extensions/dns/ruby/logger.rb index 46573c1af..142f09a99 100644 --- a/extensions/dns/ruby/logger.rb +++ b/extensions/dns/ruby/logger.rb @@ -19,5 +19,9 @@ class Logger print_error "DNS Server: #{msg}" end + def warn(msg) + print_error "DNS Server: #{msg}" + end + end From c7981f3c0d378502335a4936c3a65873df2afd69 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 7 May 2013 22:40:26 -0400 Subject: [PATCH 09/78] Demoted UPSTREAM from constant to local variable. Minimizes scope. --- extensions/dns/dns.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index c13270b7d..78b2983f2 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -11,20 +11,15 @@ module DNS include Singleton - UPSTREAM = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) - - #def initialize(address, port) - #@address = address - #@port = port - - #EventMachine::next_tick { run_server } - #end + #UPSTREAM = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) def run_server(address, port) EventMachine::next_tick do RubyDNS::run_server(:listen => [[:udp, address, port]]) do + upstream = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) + otherwise do |transaction| - transaction.passthrough!(UPSTREAM) + transaction.passthrough!(upstream) end end end From d24a00a6399caf51bdd6f0cb6f137183a8fbb6a4 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 7 May 2013 23:59:27 -0400 Subject: [PATCH 10/78] Overrode RubyDNS::Transaction.respond! to use debug logger instead. Now all RubyDNS output is properly disabled unless --verbose is given. --- extensions/dns/ruby/rubydns.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index f407b6f7e..bbf2a3669 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -5,7 +5,7 @@ # module RubyDNS - # Overrides RubyDNS::run_server() to behave exactly the same, minus the output + # Behaves exactly the same, minus the output def self.run_server(options = {}, &block) server = RubyDNS::Server.new(&block) @@ -28,4 +28,24 @@ module RubyDNS server.fire(:stop) 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 c7eb1c7fc91f62c9df56f371c81acdc4eb56e320 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 8 May 2013 00:03:08 -0400 Subject: [PATCH 11/78] Added DNS database model to load resource records from. Now modules/extensions can dynamically add new RR's. However, changes don't take effect until BeEF restarts (fix incoming). --- extensions/dns/dns.rb | 37 ++++++++++++++++++++++++++++++++-- extensions/dns/extension.rb | 1 + extensions/dns/model.rb | 24 ++++++++++++++++++++++ extensions/dns/ruby/rubydns.rb | 8 ++++++-- 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 extensions/dns/model.rb diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 78b2983f2..aa665cc53 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -11,13 +11,21 @@ module DNS include Singleton - #UPSTREAM = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) - def run_server(address, port) EventMachine::next_tick do RubyDNS::run_server(:listen => [[:udp, address, port]]) do upstream = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) + BeEF::Core::Models::DNS.each do |record| + name = record.name + type = BeEF::Extension::DNS::DNS.parse_type(record.type) + value = record.value + + match(name, type) do |transaction| + transaction.respond!(value) + end + end + otherwise do |transaction| transaction.passthrough!(upstream) end @@ -25,6 +33,31 @@ module DNS end end + def add_rule(name, type, value) + d = BeEF::Core::Models::DNS.new( + :name => name, + :type => type, + :value => value + ).save + + type = BeEF::Extension::DNS::DNS.parse_type(type) + + RubyDNS::stop_server + run_server + end + + # XXX Why must this be a class method? As a private instance method, + # it throws NoMethodError. + def self.parse_type(type) + resolv = 'Resolv::DNS::Resource' + + if type =~ /(A|AAAA|SRV|WKS)/ + resolv += '::IN' + end + + eval "#{resolv}::#{type}" + end + end end diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index 6b1375d2f..59f56543d 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -21,3 +21,4 @@ end require 'extensions/dns/api' require 'extensions/dns/dns' require 'extensions/dns/ruby' +require 'extensions/dns/model' diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb new file mode 100644 index 000000000..4c171e483 --- /dev/null +++ b/extensions/dns/model.rb @@ -0,0 +1,24 @@ +# +# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +module BeEF +module Core +module Models + + class DNS + + include DataMapper::Resource + + property :name, String + property :type, String + property :value, String + + property :id, Serial, :key => true + + end + +end +end +end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index bbf2a3669..112316667 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -16,9 +16,9 @@ module RubyDNS options[:listen].each do |spec| if spec[0] == :udp - EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, server) + @signature = EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, server) elsif spec[0] == :tcp - EventMachine.start_server(spec[1], spec[2], TCPHandler, server) + @signature = EventMachine.start_server(spec[1], spec[2], TCPHandler, server) end end @@ -28,6 +28,10 @@ module RubyDNS server.fire(:stop) end + def self.stop_server + EventMachine.stop_server(@signature) + end + class Transaction # Behaves exactly the same, except using debug logger instead of info From d622bf3e5ee4568e6322db9fa7fd348896fa9cc8 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 10 May 2013 23:01:10 -0400 Subject: [PATCH 12/78] New DNS entries can now be added dynamically without a server restart. Database is checked every five seconds and adds new rules if there were any changes. --- extensions/dns/dns.rb | 41 ++++++--------------- extensions/dns/ruby/rubydns.rb | 67 ++++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index aa665cc53..b3ceaa642 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -16,16 +16,6 @@ module DNS RubyDNS::run_server(:listen => [[:udp, address, port]]) do upstream = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) - BeEF::Core::Models::DNS.each do |record| - name = record.name - type = BeEF::Extension::DNS::DNS.parse_type(record.type) - value = record.value - - match(name, type) do |transaction| - transaction.respond!(value) - end - end - otherwise do |transaction| transaction.passthrough!(upstream) end @@ -34,28 +24,21 @@ module DNS end def add_rule(name, type, value) - d = BeEF::Core::Models::DNS.new( - :name => name, - :type => type, - :value => value - ).save + catch(:match) do + BeEF::Core::Models::DNS.each do |rule| + n = rule.name + t = rule.type + v = rule.value - type = BeEF::Extension::DNS::DNS.parse_type(type) + throw :match if [n, t, v] == [name, type, value] + end - RubyDNS::stop_server - run_server - end - - # XXX Why must this be a class method? As a private instance method, - # it throws NoMethodError. - def self.parse_type(type) - resolv = 'Resolv::DNS::Resource' - - if type =~ /(A|AAAA|SRV|WKS)/ - resolv += '::IN' + BeEF::Core::Models::DNS.create( + :name => name, + :type => type, + :value => value + ) end - - eval "#{resolv}::#{type}" end end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 112316667..39ab1af93 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -3,9 +3,14 @@ # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # + +require 'rubygems' +require 'rubydns' + module RubyDNS - # Behaves exactly the same, minus the output + # Behaves exactly the same, except without any output and an added periodic + # timer that checks for new DNS rules every five seconds def self.run_server(options = {}, &block) server = RubyDNS::Server.new(&block) @@ -16,22 +21,21 @@ module RubyDNS options[:listen].each do |spec| if spec[0] == :udp - @signature = EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, server) + EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, server) elsif spec[0] == :tcp - @signature = EventMachine.start_server(spec[1], spec[2], TCPHandler, server) + EventMachine.start_server(spec[1], spec[2], TCPHandler, server) end end + server.load_rules + EventMachine.add_periodic_timer(5) { server.check_rules } + server.fire(:start) end server.fire(:stop) end - def self.stop_server - EventMachine.stop_server(@signature) - end - class Transaction # Behaves exactly the same, except using debug logger instead of info @@ -52,4 +56,53 @@ module RubyDNS end + class Server + + # Reads current DNS entries in database and adds them as new rules + def load_rules + rules = get_rules + @rule_count = rules.count + + rules.each do |rule| + match(rule[0], parse_type(rule[1])) do |transaction| + transaction.respond!(rule[2]) + end + end + end + + # Re-loads ruleset if new entries have been added to database + def check_rules + load_rules if get_rules.count != @rule_count + end + + private + + # Returns an AoA where each element is a rule of the form [name, type, value] + def get_rules + rules = [] + + BeEF::Core::Models::DNS.each do |record| + name = record.name + type = record.type + value = record.value + + rules << [name, type, value] + end + + rules + end + + # Convenience method for fully-qualifying Resolv::DNS::Resource types + def parse_type(type) + resolv = 'Resolv::DNS::Resource' + + if type =~ /(A|AAAA|SRV|WKS)/ + resolv += '::IN' + end + + eval "#{resolv}::#{type}" + end + + end + end From 86e01b1327d1629ebf63d86f55edf489d124156b Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 10 May 2013 23:19:58 -0400 Subject: [PATCH 13/78] Documented run_server() and add_rule(). --- extensions/dns/dns.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index b3ceaa642..3aaf19863 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -11,6 +11,10 @@ module DNS include Singleton + # Starts DNS server run-loop. + # + # @param address [String] interface address server should run on + # @param port [Integer] desired server port number def run_server(address, port) EventMachine::next_tick do RubyDNS::run_server(:listen => [[:udp, address, port]]) do @@ -23,6 +27,11 @@ module DNS end end + # Adds a new DNS rule or "resource record". Does nothing if rule is already present. + # + # @param name [String] name of query + # @param type [String] query type (e.g. A, CNAME, MX, NS, etc.) + # @param value [String] response to send back to resolver def add_rule(name, type, value) catch(:match) do BeEF::Core::Models::DNS.each do |rule| From e563a8946b663973d2d1208ab6fa7771ae588083 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 14 May 2013 18:47:51 -0400 Subject: [PATCH 14/78] Began implementing new method of adding rules without periodic timer. Also added improved documentation for add_rule() and remove_rule(). --- extensions/dns/dns.rb | 83 +++++++++++++++++++++++++-------- extensions/dns/ruby/rubydns.rb | 84 ++++++++++++---------------------- 2 files changed, 93 insertions(+), 74 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 3aaf19863..9e3f4e9db 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -11,17 +11,29 @@ module DNS include Singleton - # Starts DNS server run-loop. + # @!method instance + # Returns the singleton instance. + def initialize + @server = nil + @next_id = 0 + end + + # Starts the DNS server run-loop. # # @param address [String] interface address server should run on # @param port [Integer] desired server port number def run_server(address, port) EventMachine::next_tick do RubyDNS::run_server(:listen => [[:udp, address, port]]) do - upstream = RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) + server = self + BeEF::Extension::DNS::DNS.instance.instance_eval { @server = server } + BeEF::Extension::DNS::DNS.instance.load_rules + # Pass unmatched queries upstream to root nameservers otherwise do |transaction| - transaction.passthrough!(upstream) + transaction.passthrough!( + RubyDNS::Resolver.new([[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]]) + ) end end end @@ -29,25 +41,56 @@ module DNS # Adds a new DNS rule or "resource record". Does nothing if rule is already present. # - # @param name [String] name of query - # @param type [String] query type (e.g. A, CNAME, MX, NS, etc.) - # @param value [String] response to send back to resolver - def add_rule(name, type, value) - catch(:match) do - BeEF::Core::Models::DNS.each do |rule| - n = rule.name - t = rule.type - v = rule.value + # @example Adds an A record for foobar.com with the value 1.2.3.4 + # + # dns = BeEF::Extension::DNS::DNS.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.) + # + # @yieldparam [RubyDNS::Transaction] details of query question and response + # + # @return [Integer] unique id for use with {#remove_rule} + # + # @see #remove_rule + # @see http://rubydoc.info/gems/rubydns/RubyDNS/Transaction + def add_rule(pattern, type, &block) + @next_id += 1 + @server.match(@next_id, pattern, type, block) + @next_id + end - throw :match if [n, t, v] == [name, type, value] - end + # Removes the given DNS rule. Any future queries for it will be passed through. + # + # @param id [Integer] id returned from {#add_rule} + # @see #add_rule + def remove_rule(id) + @server.remove_rule(id) + @next_id -= 1 + end - BeEF::Core::Models::DNS.create( - :name => name, - :type => type, - :value => value - ) - end + # Loads all rules from the database at server startup + def load_rules + # TODO Load rules from database + end + + private + + # Convenience method for fully-qualifying Resolv::DNS::Resource::IN types + def parse_type(type) + eval "Resolv::DNS::Resource::IN::#{type}" + end + + # Convenience method for generating proper server responses + def parse_response(type, value) + response = 'value.to_s' + response = 'Resolv::DNS::Name.create(value)' if type =~ /(CNAME|NS|PTR)/ + + eval response end end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 39ab1af93..b7a4bdf4c 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -9,8 +9,7 @@ require 'rubydns' module RubyDNS - # Behaves exactly the same, except without any output and an added periodic - # timer that checks for new DNS rules every five seconds + # Behaves exactly the same, except without any logger output def self.run_server(options = {}, &block) server = RubyDNS::Server.new(&block) @@ -27,15 +26,41 @@ module RubyDNS end end - server.load_rules - EventMachine.add_periodic_timer(5) { server.check_rules } - server.fire(:start) end server.fire(:stop) end + class Server + + attr_accessor :rules + + 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 + + # Now uses an 'id' parameter to uniquely identify rules + def match(id, *pattern, block) + @rules << Rule.new(id, pattern, block) + end + + # New method that removes a rule given its id + def remove_rule(id) + @rules.delete_if { |rule| rule.id == id } + end + + end + class Transaction # Behaves exactly the same, except using debug logger instead of info @@ -56,53 +81,4 @@ module RubyDNS end - class Server - - # Reads current DNS entries in database and adds them as new rules - def load_rules - rules = get_rules - @rule_count = rules.count - - rules.each do |rule| - match(rule[0], parse_type(rule[1])) do |transaction| - transaction.respond!(rule[2]) - end - end - end - - # Re-loads ruleset if new entries have been added to database - def check_rules - load_rules if get_rules.count != @rule_count - end - - private - - # Returns an AoA where each element is a rule of the form [name, type, value] - def get_rules - rules = [] - - BeEF::Core::Models::DNS.each do |record| - name = record.name - type = record.type - value = record.value - - rules << [name, type, value] - end - - rules - end - - # Convenience method for fully-qualifying Resolv::DNS::Resource types - def parse_type(type) - resolv = 'Resolv::DNS::Resource' - - if type =~ /(A|AAAA|SRV|WKS)/ - resolv += '::IN' - end - - eval "#{resolv}::#{type}" - end - - end - end From ddcb040c40f624ee4fb4a3143e4bd95ab2ca61c9 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 14 May 2013 19:12:23 -0400 Subject: [PATCH 15/78] Marked add_rule() and remove_rule() as critical sections. Mutual exclusion is imperative here since other modules/extenions may be simultaneously adding/removing rules, thus putting the value of @next_id at risk of becoming inconsistent. --- extensions/dns/dns.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 9e3f4e9db..ac646189a 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -14,7 +14,8 @@ module DNS # @!method instance # Returns the singleton instance. def initialize - @server = nil + @lock = Mutex.new + @server = nil @next_id = 0 end @@ -59,9 +60,11 @@ module DNS # @see #remove_rule # @see http://rubydoc.info/gems/rubydns/RubyDNS/Transaction def add_rule(pattern, type, &block) - @next_id += 1 - @server.match(@next_id, pattern, type, block) - @next_id + @lock.synchronize do + @next_id += 1 + @server.match(@next_id, pattern, type, block) + @next_id + end end # Removes the given DNS rule. Any future queries for it will be passed through. @@ -69,8 +72,10 @@ module DNS # @param id [Integer] id returned from {#add_rule} # @see #add_rule def remove_rule(id) - @server.remove_rule(id) - @next_id -= 1 + @lock.synchronize do + @server.remove_rule(id) + @next_id -= 1 + end end # Loads all rules from the database at server startup From 1f7e748afc9595aba57044f6a0d9bf11c1648756 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 14 May 2013 19:23:08 -0400 Subject: [PATCH 16/78] Removed parse_response() since it's no longer needed. --- extensions/dns/dns.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index ac646189a..9832961f2 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -90,14 +90,6 @@ module DNS eval "Resolv::DNS::Resource::IN::#{type}" end - # Convenience method for generating proper server responses - def parse_response(type, value) - response = 'value.to_s' - response = 'Resolv::DNS::Name.create(value)' if type =~ /(CNAME|NS|PTR)/ - - eval response - end - end end From 992e95f0d7ca8cb578e21f1d2bbe26ab5786fee3 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 15 May 2013 22:12:37 -0400 Subject: [PATCH 17/78] Added database support when adding/removing rules. Needed to add 'sourcify' as a dependency in order to store code blocks in the database. --- Gemfile | 1 + core/loader.rb | 1 + extensions/dns/dns.rb | 1 - extensions/dns/model.rb | 8 +++---- extensions/dns/ruby/rubydns.rb | 40 ++++++++++++++++++++++++++++------ 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 16b8678b0..c7006f14d 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem "erubis" gem "dm-migrations" gem "msfrpc-client" gem "rubydns" +gem "sourcify" # notifications gem "twitter" diff --git a/core/loader.rb b/core/loader.rb index 01e284326..936745e04 100644 --- a/core/loader.rb +++ b/core/loader.rb @@ -16,6 +16,7 @@ require 'base64' require 'xmlrpc/client' require 'openssl' require 'rubydns' +require 'sourcify' # @note Include the filters require 'core/filters' diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 9832961f2..4ff25a638 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -28,7 +28,6 @@ module DNS RubyDNS::run_server(:listen => [[:udp, address, port]]) do server = self BeEF::Extension::DNS::DNS.instance.instance_eval { @server = server } - BeEF::Extension::DNS::DNS.instance.load_rules # Pass unmatched queries upstream to root nameservers otherwise do |transaction| diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 4c171e483..41068c4da 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -11,11 +11,11 @@ module Models include DataMapper::Resource - property :name, String - property :type, String - property :value, String + storage_names[:default] = 'extensions_dns' - property :id, Serial, :key => true + property :id, Serial + property :pattern, Object + property :block, Text end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index b7a4bdf4c..25109dd36 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -3,10 +3,6 @@ # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # - -require 'rubygems' -require 'rubydns' - module RubyDNS # Behaves exactly the same, except without any logger output @@ -26,6 +22,7 @@ module RubyDNS end end + server.load_rules server.fire(:start) end @@ -51,12 +48,41 @@ module RubyDNS # Now uses an 'id' parameter to uniquely identify rules def match(id, *pattern, block) - @rules << Rule.new(id, pattern, block) + catch :match do + # Check if rule is already present + BeEF::Core::Models::DNS.each { |rule| throw :match if rule.id == id } + + @rules << Rule.new(id, pattern, block) + + # Add new rule to database + BeEF::Core::Models::DNS.create( + :id => id, + :pattern => pattern, + :block => block.to_source + ) + end end # New method that removes a rule given its id def remove_rule(id) @rules.delete_if { |rule| rule.id == id } + + begin + BeEF::Core::Models::DNS.get!(id).destroy + rescue DataMapper::ObjectNotFoundError => e + @logger.error(e.message) + end + end + + # New method that loads all rules from the database at server startup + def load_rules + BeEF::Core::Models::DNS.each do |rule| + id = rule.id + pattern = rule.pattern + block = eval rule.block + + @rules << Rule.new(id, pattern, block) + end end end @@ -72,9 +98,9 @@ module RubyDNS raise ArgumentError, "Could not instantiate resource #{resource_class}!" end - @server.logger.debug "Resource class: #{resource_class.inspect}" + @server.logger.debug("Resource class: #{resource_class.inspect}") resource = resource_class.new(*data) - @server.logger.debug "Resource: #{resource.inspect}" + @server.logger.debug("Resource: #{resource.inspect}") append!(resource, options) end From 872ce2e92fd8e816750f500e347e0433a262e028 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 15 May 2013 22:15:50 -0400 Subject: [PATCH 18/78] Updated README to mention rubydns and sourcify dependencies. --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index c584cbd55..a72f8ef52 100644 --- a/README +++ b/README @@ -52,6 +52,8 @@ Requirements - dm-migrations - msfrpc-client - eventmachine + - rubydns + - sourcify - win32console (Windows Only) From 35f25bbeb9ab13cac5c80df6228993fd68ccfeb0 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 15 May 2013 22:18:16 -0400 Subject: [PATCH 19/78] Removed load_rules() and parse_type() since they're unused. --- extensions/dns/dns.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 4ff25a638..75483662d 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -77,18 +77,6 @@ module DNS end end - # Loads all rules from the database at server startup - def load_rules - # TODO Load rules from database - end - - private - - # Convenience method for fully-qualifying Resolv::DNS::Resource::IN types - def parse_type(type) - eval "Resolv::DNS::Resource::IN::#{type}" - end - end end From 271b2b8e85a34047509ca72318380bcd86f8dd7f Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 15 May 2013 22:19:58 -0400 Subject: [PATCH 20/78] Removed RubyDNS::Server#rules attribute accessor since it's unused. --- extensions/dns/ruby/rubydns.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 25109dd36..899aeb17d 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -31,8 +31,6 @@ module RubyDNS class Server - attr_accessor :rules - class Rule attr_accessor :id From 6d2a7710844c4c1bddc8897ed3ba129612d0da19 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 15 May 2013 22:29:42 -0400 Subject: [PATCH 21/78] Changed model name to BeEF::Core::Models::DNS::Rule. This is more descriptive and follows the singular name convention. --- extensions/dns/model.rb | 6 ++++-- extensions/dns/ruby/rubydns.rb | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 41068c4da..2a03f2fc8 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -6,12 +6,13 @@ module BeEF module Core module Models +module DNS - class DNS + class Rule include DataMapper::Resource - storage_names[:default] = 'extensions_dns' + storage_names[:default] = 'extension_dns_rules' property :id, Serial property :pattern, Object @@ -22,3 +23,4 @@ module Models end end end +end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 899aeb17d..3dbc837e8 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -48,12 +48,12 @@ module RubyDNS def match(id, *pattern, block) catch :match do # Check if rule is already present - BeEF::Core::Models::DNS.each { |rule| throw :match if rule.id == id } + BeEF::Core::Models::DNS::Rule.each { |rule| throw :match if rule.id == id } @rules << Rule.new(id, pattern, block) # Add new rule to database - BeEF::Core::Models::DNS.create( + BeEF::Core::Models::DNS::Rule.create( :id => id, :pattern => pattern, :block => block.to_source @@ -66,7 +66,7 @@ module RubyDNS @rules.delete_if { |rule| rule.id == id } begin - BeEF::Core::Models::DNS.get!(id).destroy + BeEF::Core::Models::DNS::Rule.get!(id).destroy rescue DataMapper::ObjectNotFoundError => e @logger.error(e.message) end @@ -74,7 +74,7 @@ module RubyDNS # New method that loads all rules from the database at server startup def load_rules - BeEF::Core::Models::DNS.each do |rule| + BeEF::Core::Models::DNS::Rule.each do |rule| id = rule.id pattern = rule.pattern block = eval rule.block From 24f7e5b6cd7bfe9e81a40a3e40de84685a2f7968 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 16 May 2013 23:14:29 -0400 Subject: [PATCH 22/78] Separated 'pattern' and 'type' properties in DNS model. This will expose the resource type to the RESTful API (coming soon). --- extensions/dns/model.rb | 7 ++++--- extensions/dns/ruby/rubydns.rb | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 2a03f2fc8..84ac3555a 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -14,9 +14,10 @@ module DNS storage_names[:default] = 'extension_dns_rules' - property :id, Serial - property :pattern, Object - property :block, Text + property :id, Serial # Unique identifier + property :pattern, Object # Query pattern + property :type, Object # Resource type + property :block, Text # Associated callback end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 3dbc837e8..fb5e21c62 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -55,7 +55,8 @@ module RubyDNS # Add new rule to database BeEF::Core::Models::DNS::Rule.create( :id => id, - :pattern => pattern, + :pattern => pattern[0], + :type => pattern[1], :block => block.to_source ) end From 18a78b57b2548cb0d102208099b1ab7becc33486 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 16 May 2013 23:20:04 -0400 Subject: [PATCH 23/78] Fixed load_rules() to rebuild 'pattern' and 'type' as an array. This was forgotten in the previous commit. --- extensions/dns/ruby/rubydns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index fb5e21c62..222c6aec8 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -77,7 +77,7 @@ module RubyDNS def load_rules BeEF::Core::Models::DNS::Rule.each do |rule| id = rule.id - pattern = rule.pattern + pattern = [rule.pattern, rule.type] block = eval rule.block @rules << Rule.new(id, pattern, block) From 13001b96420003e26247b678cd9cfd8de7304d67 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 16 May 2013 23:24:23 -0400 Subject: [PATCH 24/78] Updated README.mkd to mention rubydns and sourcify dependencies. This was forgettin in commit 872ce2e. --- README.mkd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.mkd b/README.mkd index fdeb6146d..fc3703278 100644 --- a/README.mkd +++ b/README.mkd @@ -52,6 +52,8 @@ Requirements - dm-migrations - msfrpc-client - eventmachine + - rubydns + - sourcify - win32console (Windows Only) From c70037f9f4b434b2a4d7f67e3a311e94c18776b5 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 17 May 2013 18:25:22 -0400 Subject: [PATCH 25/78] Began adding support for RESTful API beginning with /api/dns/rules. --- extensions/dns/api.rb | 20 ++++++++++-- extensions/dns/dns.rb | 28 ++++++++++++++++ extensions/dns/extension.rb | 3 +- extensions/dns/rest/dns.rb | 65 +++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 extensions/dns/rest/dns.rb diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 1161a7ae8..5177026f2 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -10,10 +10,19 @@ module API 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' + ) + + # Begins main DNS server run-loop at BeEF startup def self.pre_http_start(http_hook_server) config = BeEF::Core::Configuration.instance @@ -28,6 +37,11 @@ module API 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 diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 75483662d..c8ec1a5a5 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -77,6 +77,34 @@ module DNS end end + # Returns an AoH representing the entire current DNS ruleset where each element is a + # hash with the following keys: + # + # * :id + # * :pattern + # * :type + # * :block + # + # @return [Array] DNS ruleset (empty if no rules are currently loaded) + def get_rules + @lock.synchronize do + result = [] + + BeEF::Core::Models::DNS::Rule.each do |rule| + element = {} + + element[:id] = rule.id + element[:pattern] = rule.pattern + element[:type] = rule.type + element[:block] = rule.block + + result << element + end + + result + end + end + end end diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index 59f56543d..9618bd5f9 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -20,5 +20,6 @@ end require 'extensions/dns/api' require 'extensions/dns/dns' -require 'extensions/dns/ruby' require 'extensions/dns/model' +require 'extensions/dns/rest/dns' +require 'extensions/dns/ruby' diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb new file mode 100644 index 000000000..74fc69c15 --- /dev/null +++ b/extensions/dns/rest/dns.rb @@ -0,0 +1,65 @@ +# +# Copyright (c) 2006-2013 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +# GET: +# * Rule count +# * List of rules + +# POST: +# * Add rule +# * Remove rule + + +# /api/dns/rules +# { +# "rules": [ +# { +# "id": 1, +# "pattern": "foobar.com", +# "type": "Resolv::DNS::Resource::IN::A" +# "block": "proc {|t| ...do shit... }" +# }, +# +# { +# }, +# +# { +# }, +# ] +# } + + + +module BeEF +module Extension +module DNS + + class DNSRest < BeEF::Core::Router::Router + + before do + config = BeEF::Core::Configuration.instance + + error 401 unless params[:token] == config.get('beef.api_token') + halt 401 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 '/rules' do + result = {} + result[:rules] = BeEF::Extension::DNS::DNS.instance.get_rules + result.to_json + end + + end + +end +end +end From 702595c04c42da17969d8061794bcca7a6ae0363 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 17 May 2013 19:12:05 -0400 Subject: [PATCH 26/78] Improved a lot of documentation for BeEF::Extension::DNS::DNS. --- extensions/dns/dns.rb | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index c8ec1a5a5..0be4a963c 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -7,23 +7,35 @@ 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 DNS include Singleton # @!method instance - # Returns the singleton 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 @next_id = 0 end - # Starts the DNS server run-loop. + # Starts the main DNS server run-loop. + # + # @note This method will not return. It is recommended that it be invoked inside a + # separate thread. # # @param address [String] interface address server should run on # @param port [Integer] desired server port number - def run_server(address, port) + def run_server(address = '0.0.0.0', port = 5300) EventMachine::next_tick do RubyDNS::run_server(:listen => [[:udp, address, port]]) do server = self @@ -52,9 +64,10 @@ module DNS # @param pattern [String, Regexp] query pattern to recognize # @param type [Resolv::DNS::Resource::IN] resource record type (e.g. A, CNAME, NS, etc.) # - # @yieldparam [RubyDNS::Transaction] details of query question and response + # @yield callback to invoke when pattern is matched + # @yieldparam transaction [RubyDNS::Transaction] details of query question and response # - # @return [Integer] unique id for use with {#remove_rule} + # @return [Integer] unique identifier for use with {#remove_rule} # # @see #remove_rule # @see http://rubydoc.info/gems/rubydns/RubyDNS/Transaction @@ -77,8 +90,9 @@ module DNS end end - # Returns an AoH representing the entire current DNS ruleset where each element is a - # hash with the following keys: + # Returns an AoH representing the entire current DNS ruleset. + # + # Each element is a hash with the following keys: # # * :id # * :pattern From 054767c898e6f47348c2b9211ea333ba198689e3 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 17 May 2013 23:02:40 -0400 Subject: [PATCH 27/78] Added RESTful API route for /api/dns/rule/:id. This will return a single rule given its unique id. --- extensions/dns/dns.rb | 19 +++++++++++--- extensions/dns/rest/dns.rb | 46 +++++++++++----------------------- extensions/dns/ruby/rubydns.rb | 18 +++++++++++++ 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 0be4a963c..8580fa02b 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -17,11 +17,11 @@ module DNS include Singleton - # @!method instance + # @!method self.instance # Returns the singleton instance. Use this in place of {#initialize}. - # @note This method cannot be invoked! Use {#instance} instead. - # @see #instance + # @note This method cannot be invoked! Use {.instance} instead. + # @see ::instance def initialize @lock = Mutex.new @server = nil @@ -100,7 +100,7 @@ module DNS # * :block # # @return [Array] DNS ruleset (empty if no rules are currently loaded) - def get_rules + def get_ruleset @lock.synchronize do result = [] @@ -119,6 +119,17 @@ module DNS end 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 do + @server.get_rule(id) + end + end + end end diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 74fc69c15..dcf7e0849 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -3,45 +3,18 @@ # Browser Exploitation Framework (BeEF) - http://beefproject.com # See the file 'doc/COPYING' for copying permission # - -# GET: -# * Rule count -# * List of rules - -# POST: -# * Add rule -# * Remove rule - - -# /api/dns/rules -# { -# "rules": [ -# { -# "id": 1, -# "pattern": "foobar.com", -# "type": "Resolv::DNS::Resource::IN::A" -# "block": "proc {|t| ...do shit... }" -# }, -# -# { -# }, -# -# { -# }, -# ] -# } - - - module BeEF 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 + # 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 error 401 unless params[:token] == config.get('beef.api_token') halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) @@ -53,8 +26,17 @@ module DNS # Returns the entire current DNS ruleset get '/rules' do - result = {} - result[:rules] = BeEF::Extension::DNS::DNS.instance.get_rules + result[:rules] = BeEF::Extension::DNS::DNS.instance.get_ruleset + result.to_json + end + + # Returns a specific rule given its id + get '/rule/:id' do + id = params[:id] + + halt 401 unless BeEF::Filters.nums_only?(id) + + result = BeEF::Extension::DNS::DNS.instance.get_rule(id) result.to_json end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 222c6aec8..0af5744c0 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -84,6 +84,24 @@ module RubyDNS end 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 + result[:block] = rule.block + rescue DataMapper::ObjectNotFoundError => e + @logger.error(e.message) + end + + result + end + end class Transaction From c6f38324d1143f6985618c9b557ced61b10b434a Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sat, 18 May 2013 21:00:22 -0400 Subject: [PATCH 28/78] Refactored #get_ruleset to be part of RubyDNS. All database logic should be inside RubyDNS since BeEF's DNS class is mostly just a wrapper around it. --- extensions/dns/dns.rb | 15 +-------------- extensions/dns/rest/dns.rb | 1 + extensions/dns/ruby/rubydns.rb | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 8580fa02b..0896846b5 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -102,20 +102,7 @@ module DNS # @return [Array] DNS ruleset (empty if no rules are currently loaded) def get_ruleset @lock.synchronize do - result = [] - - BeEF::Core::Models::DNS::Rule.each do |rule| - element = {} - - element[:id] = rule.id - element[:pattern] = rule.pattern - element[:type] = rule.type - element[:block] = rule.block - - result << element - end - - result + @server.get_ruleset end end diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index dcf7e0849..a329f466b 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -26,6 +26,7 @@ module DNS # Returns the entire current DNS ruleset get '/rules' do + result = {} result[:rules] = BeEF::Extension::DNS::DNS.instance.get_ruleset result.to_json end diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 0af5744c0..3d8de48b1 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -84,6 +84,24 @@ module RubyDNS end 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 + element[:block] = rule.block + + result << element + end + + result + end + # New method that returns a hash representing the given rule def get_rule(id) result = {} From 27b1b530ef542302128fe17359f9de97b445dd90 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 26 May 2013 22:44:11 -0400 Subject: [PATCH 29/78] Implemented POST handler for /api/dns/rule which adds a new rule. A host of other changes got roped into this as well. #match now silently handles blocks passed as a String in order to handle the 'block' JSON parameter. This is because sourcify doesn't work with eval'd data. Rule id's are no longer incremental integers. It's now a 7-character "token" generated from #secure_token and is managed by the RubyDNS module. --- extensions/dns/dns.rb | 6 +-- extensions/dns/model.rb | 8 ++-- extensions/dns/rest/dns.rb | 61 ++++++++++++++++++++++-- extensions/dns/ruby/logger.rb | 2 +- extensions/dns/ruby/rubydns.rb | 86 +++++++++++++++++++++++++++++----- 5 files changed, 137 insertions(+), 26 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 0896846b5..51ae3a256 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -25,7 +25,6 @@ module DNS def initialize @lock = Mutex.new @server = nil - @next_id = 0 end # Starts the main DNS server run-loop. @@ -73,9 +72,7 @@ module DNS # @see http://rubydoc.info/gems/rubydns/RubyDNS/Transaction def add_rule(pattern, type, &block) @lock.synchronize do - @next_id += 1 - @server.match(@next_id, pattern, type, block) - @next_id + return @server.match(pattern, type, block) end end @@ -86,7 +83,6 @@ module DNS def remove_rule(id) @lock.synchronize do @server.remove_rule(id) - @next_id -= 1 end end diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 84ac3555a..7afea0c24 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -14,10 +14,10 @@ module DNS storage_names[:default] = 'extension_dns_rules' - property :id, Serial # 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 diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index a329f466b..a98cd62c9 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -33,12 +33,65 @@ module DNS # Returns a specific rule given its id get '/rule/:id' do - id = params[:id] + begin + id = params[:id] - halt 401 unless BeEF::Filters.nums_only?(id) + unless BeEF::Filters.alphanums_only?(id) + raise StandardError, 'Invalid id passed to endpoint /api/dns/rule/:id' + end - result = BeEF::Extension::DNS::DNS.instance.get_rule(id) - result.to_json + result = BeEF::Extension::DNS::DNS.instance.get_rule(id) + result.to_json + rescue StandardError => e + print_error e.message + halt 400 + end + end + + # Adds a new DNS rule + post '/rule' do + begin + body = JSON.parse(request.body.read) + + pattern = body['pattern'] + type = eval body['type'] + block = body['block'] + + # Validate required JSON keys + unless [pattern, type, block].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 + + if type.superclass != Resolv::DNS::Resource + raise StandardError, 'Invalid resource type given in "type" key' + end + + unless BeEF::Filters.is_non_empty_string?(block) + raise StandardError, 'Invalid code block given in "block" key' + end + + id = '' + + # Bypass #add_rule so that 'block' can be passed as a String + BeEF::Extension::DNS::DNS.instance.instance_eval do + id = @server.match(pattern, type, block) + end + + result = {} + result['success'] = true + result['id'] = id + result.to_json + end + rescue StandardError => e + print_error e.message + halt 400 + rescue Exception => e + print_error "Invalid JSON input passed to endpoint /api/dns/rule" + halt 400 + end end end diff --git a/extensions/dns/ruby/logger.rb b/extensions/dns/ruby/logger.rb index 142f09a99..3e88cbfa0 100644 --- a/extensions/dns/ruby/logger.rb +++ b/extensions/dns/ruby/logger.rb @@ -4,7 +4,7 @@ # See the file 'doc/COPYING' for copying permission # -# Overrives the logger used by RubyDNS to use BeEF's print_info() and friends +# Overrives the logger used by RubyDNS to use BeEF's {#print_info} and friends. class Logger def debug(msg) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 3d8de48b1..d88996936 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -3,6 +3,17 @@ # 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::DNS 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 @@ -33,6 +44,7 @@ module RubyDNS class Rule + # XXX Can this be removed? attr_accessor :id # Now uses an 'id' parameter to uniquely identify rules @@ -44,22 +56,56 @@ module RubyDNS end - # Now uses an 'id' parameter to uniquely identify rules - def match(id, *pattern, block) + # Now includes BeEF database support and checks for already present rules + def match(*pattern, block) + id = '' + catch :match do - # Check if rule is already present - BeEF::Core::Models::DNS::Rule.each { |rule| throw :match if rule.id == id } + begin + # Sourcify block (already a string only for RESTful API calls) + block_src = case block.class.name + when String then block + when Proc then block.to_source + end - @rules << Rule.new(id, pattern, block) + # 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 - # Add new rule to database - BeEF::Core::Models::DNS::Rule.create( - :id => id, - :pattern => pattern[0], - :type => pattern[1], - :block => block.to_source - ) + id = rule.id + throw :match + end + end + + id = generate_id + + case block.class.name + 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], + :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 @@ -120,6 +166,22 @@ module RubyDNS result 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 + end class Transaction From 38284d5eaa08852973bcc691f991176802da674a Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 26 May 2013 23:26:58 -0400 Subject: [PATCH 30/78] Implemented DELETE handler for removing DNS rules. --- extensions/dns/rest/dns.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index a98cd62c9..026cf9deb 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -89,7 +89,23 @@ module DNS print_error e.message halt 400 rescue Exception => e - print_error "Invalid JSON input passed to endpoint /api/dns/rule" + print_error 'Invalid JSON input passed to endpoint /api/dns/rule' + halt 400 + end + end + + # Removes a rule given its id + delete '/rule/:id' do + begin + id = params[:id] + + unless BeEF::Filters.alphanums_only?(id) + raise StandardError, 'Invalid id passed to endpoint /api/dns/rule/:id' + end + + BeEF::Extension::DNS::DNS.instance.remove_rule(id) + rescue StandardError => e + print_error e.message halt 400 end end From 7f4562945a9c2c16ef68ea0ea08b0b4d06d39ed0 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 26 May 2013 23:46:37 -0400 Subject: [PATCH 31/78] Added new InvalidJsonError class for handling errors in JSON input. This is better practice than just (ab)using StandardError. --- extensions/dns/rest/dns.rb | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 026cf9deb..2dcc0a192 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -37,12 +37,12 @@ module DNS id = params[:id] unless BeEF::Filters.alphanums_only?(id) - raise StandardError, 'Invalid id passed to endpoint /api/dns/rule/:id' + raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' end result = BeEF::Extension::DNS::DNS.instance.get_rule(id) result.to_json - rescue StandardError => e + rescue InvalidJsonError => e print_error e.message halt 400 end @@ -66,11 +66,11 @@ module DNS rescue => e; end if type.superclass != Resolv::DNS::Resource - raise StandardError, 'Invalid resource type given in "type" key' + raise InvalidJsonError, 'Invalid "type" key passed to endpoint /api/dns/rule' end unless BeEF::Filters.is_non_empty_string?(block) - raise StandardError, 'Invalid code block given in "block" key' + raise InvalidJsonError, 'Invalid "block" key passed to endpoint /api/dns/rule' end id = '' @@ -85,7 +85,7 @@ module DNS result['id'] = id result.to_json end - rescue StandardError => e + rescue InvalidJsonError => e print_error e.message halt 400 rescue Exception => e @@ -100,16 +100,27 @@ module DNS id = params[:id] unless BeEF::Filters.alphanums_only?(id) - raise StandardError, 'Invalid id passed to endpoint /api/dns/rule/:id' + raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' end BeEF::Extension::DNS::DNS.instance.remove_rule(id) - rescue StandardError => e + rescue InvalidJsonError => e print_error e.message halt 400 end 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 + end end From 998980b5667681d711c924b5fd0a9eb05b64314c Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 2 Jun 2013 22:23:27 -0400 Subject: [PATCH 32/78] Fixed case-statement in #match that prevented adding rules locally. --- extensions/dns/ruby/rubydns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index d88996936..af2990c47 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -63,7 +63,7 @@ module RubyDNS catch :match do begin # Sourcify block (already a string only for RESTful API calls) - block_src = case block.class.name + block_src = case block when String then block when Proc then block.to_source end From c8c9e1e139849d2bd8494f8619178fb06c53a3f9 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 2 Jun 2013 22:40:58 -0400 Subject: [PATCH 33/78] Reimplemented POST handler to avoid unsafe use of #eval. Now the desired response is passed an array. Each RR type is handled specially to craft the necessary response. --- extensions/dns/rest/dns.rb | 105 +++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 2dcc0a192..2df9982d7 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -54,30 +54,41 @@ module DNS body = JSON.parse(request.body.read) pattern = body['pattern'] - type = eval body['type'] - block = body['block'] + type = body['type'] + response = body['response'] # Validate required JSON keys - unless [pattern, type, block].include?(nil) + 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 - if type.superclass != Resolv::DNS::Resource - raise InvalidJsonError, 'Invalid "type" 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?(block) - raise InvalidJsonError, 'Invalid "block" key passed to endpoint /api/dns/rule' + 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 = '' - # Bypass #add_rule so that 'block' can be passed as a String + type_obj = eval "Resolv::DNS::Resource::IN::#{type}" + block_src = format_response(type, response) + + # Bypass #add_rule so that 'block_src' can be passed as a String BeEF::Extension::DNS::DNS.instance.instance_eval do - id = @server.match(pattern, type, block) + id = @server.match(pattern, type_obj, block_src) end result = {} @@ -89,8 +100,8 @@ module DNS print_error e.message halt 400 rescue Exception => e - print_error 'Invalid JSON input passed to endpoint /api/dns/rule' - halt 400 + print_error "Internal error while adding DNS rule (#{e.message})" + halt 500 end end @@ -110,6 +121,78 @@ module DNS 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) }" + + src % case type + when 'A' + data = { :address => rdata[0] } + "'%
s'" % data + when 'AAAA' + data = { :address => rdata[0] } + "'%
s'" % data + when 'CNAME' + data = { :cname => rdata[0] } + "Resolv::DNS::Name.create('%s')" % data + when 'HINFO' + data = { :cpu => rdata[0], :os => rdata[1] } + "'%s', '%s'" % data + when 'MINFO' + data = { :rmailbx => rdata[0], :emailbx => rdata[1] } + + "Resolv::DNS::Name.create('%s'), " \ + "Resolv::DNS::Name.create('%s')" % data + when 'MX' + data = { :preference => rdata[0], :exchange => rdata[1] } + "'%d', Resolv::DNS::Name.create('%s')" % data + when 'NS' + data = { :nsdname => rdata[0] } + "Resolv::DNS::Name.create('%s')" % data + when 'PTR' + data = { :ptrdname => rdata[0] } + "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] + } + + "Resolv::DNS::Name.create('%s'), " \ + "Resolv::DNS::Name.create('%s'), " \ + "%d, " \ + "%d, " \ + "%d, " \ + "%d, " \ + "%d" % data + when 'TXT' + data = { :txtdata => rdata[0] } + "'%s'" % data + when 'WKS' + data = { + :address => rdata[0], + :protocol => rdata[1], + :bitmap => rdata[2] + } + + "'%
s', %d, %d" % data + else + raise InvalidJsonError, 'Unknown "type" key passed to endpoint /api/dns/rule' + end + end + # Raised when invalid JSON input is passed to an /api/dns handler. class InvalidJsonError < StandardError From 0f8221918bec5930dbb81f0685eabd7729c8e944 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 3 Jun 2013 00:11:41 -0400 Subject: [PATCH 34/78] Improved coding style (a la ruby-style-guide and rubocop). Because I'm too tired to start testing and need a little victory. ;) --- extensions/dns/api.rb | 2 +- extensions/dns/dns.rb | 10 +-- extensions/dns/extension.rb | 4 +- extensions/dns/rest/dns.rb | 128 +++++++++++++++++---------------- extensions/dns/ruby/rubydns.rb | 19 ++--- 5 files changed, 84 insertions(+), 79 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 5177026f2..231ad993d 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -30,7 +30,7 @@ module API port = config.get('beef.extension.dns.port') Thread.new do - dns = BeEF::Extension::DNS::DNS.instance + dns = BeEF::Extension::DNS::Server.instance dns.run_server(address, port) end diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 51ae3a256..7fa0f6ab9 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -13,7 +13,7 @@ module DNS # # 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 DNS + class Server include Singleton @@ -35,10 +35,10 @@ module DNS # @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) - EventMachine::next_tick do - RubyDNS::run_server(:listen => [[:udp, address, port]]) do + EventMachine.next_tick do + RubyDNS.run_server(:listen => [[:udp, address, port]]) do server = self - BeEF::Extension::DNS::DNS.instance.instance_eval { @server = server } + BeEF::Extension::DNS::Server.instance.instance_eval { @server = server } # Pass unmatched queries upstream to root nameservers otherwise do |transaction| @@ -54,7 +54,7 @@ module DNS # # @example Adds an A record for foobar.com with the value 1.2.3.4 # - # dns = BeEF::Extension::DNS::DNS.instance + # 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') diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index 9618bd5f9..c725fd1d5 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -11,9 +11,9 @@ module DNS @short_name = 'dns' @full_name = 'DNS Server' - @description = 'A configurable DNS nameserver for performing DNS spoofing, ' \ + @description = 'A configurable DNS nameserver for performing DNS spoofing, ' + 'hijacking, and other related attacks against hooked zombies' - + end end end diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 2df9982d7..cec32c7d7 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -27,7 +27,7 @@ module DNS # Returns the entire current DNS ruleset get '/rules' do result = {} - result[:rules] = BeEF::Extension::DNS::DNS.instance.get_ruleset + result[:rules] = BeEF::Extension::DNS::Server.instance.get_ruleset result.to_json end @@ -40,7 +40,7 @@ module DNS raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' end - result = BeEF::Extension::DNS::DNS.instance.get_rule(id) + result = BeEF::Extension::DNS::Server.instance.get_rule(id) result.to_json rescue InvalidJsonError => e print_error e.message @@ -87,7 +87,7 @@ module DNS block_src = format_response(type, response) # Bypass #add_rule so that 'block_src' can be passed as a String - BeEF::Extension::DNS::DNS.instance.instance_eval do + BeEF::Extension::DNS::Server.instance.instance_eval do id = @server.match(pattern, type_obj, block_src) end @@ -99,7 +99,7 @@ module DNS rescue InvalidJsonError => e print_error e.message halt 400 - rescue Exception => e + rescue StandardError => e print_error "Internal error while adding DNS rule (#{e.message})" halt 500 end @@ -114,7 +114,7 @@ module DNS raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' end - BeEF::Extension::DNS::DNS.instance.remove_rule(id) + BeEF::Extension::DNS::Server.instance.remove_rule(id) rescue InvalidJsonError => e print_error e.message halt 400 @@ -130,67 +130,71 @@ module DNS # # @return [String] string representation of response callback def format_response(type, rdata) - src = "proc { |t| t.respond!(%s) }" + src = 'proc { |t| t.respond!(%s) }' - src % case type - when 'A' - data = { :address => rdata[0] } - "'%
s'" % data - when 'AAAA' - data = { :address => rdata[0] } - "'%
s'" % data - when 'CNAME' - data = { :cname => rdata[0] } - "Resolv::DNS::Name.create('%s')" % data - when 'HINFO' - data = { :cpu => rdata[0], :os => rdata[1] } - "'%s', '%s'" % data - when 'MINFO' - data = { :rmailbx => rdata[0], :emailbx => rdata[1] } + 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] } - "Resolv::DNS::Name.create('%s'), " \ - "Resolv::DNS::Name.create('%s')" % data - when 'MX' - data = { :preference => rdata[0], :exchange => rdata[1] } - "'%d', Resolv::DNS::Name.create('%s')" % data - when 'NS' - data = { :nsdname => rdata[0] } - "Resolv::DNS::Name.create('%s')" % data - when 'PTR' - data = { :ptrdname => rdata[0] } - "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')", + 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] + } - "Resolv::DNS::Name.create('%s'), " \ - "Resolv::DNS::Name.create('%s'), " \ - "%d, " \ - "%d, " \ - "%d, " \ - "%d, " \ - "%d" % data - when 'TXT' - data = { :txtdata => rdata[0] } - "'%s'" % data - when 'WKS' - data = { - :address => rdata[0], - :protocol => rdata[1], - :bitmap => rdata[2] - } + 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] + } - "'%
s', %d, %d" % data - else - raise InvalidJsonError, 'Unknown "type" key passed to endpoint /api/dns/rule' - end + 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. diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index af2990c47..309e1cfb1 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -11,7 +11,7 @@ # identifiers to rules, rule removal, and more. # # The core functionality of BeEF's DNS server is implemented here, whereas -# BeEF::Extension::DNS::DNS is simply a small wrapper around it. +# BeEF::Extension::DNS::Server is simply a small wrapper around it. # # @see http://rubydoc.info/gems/rubydns/frames module RubyDNS @@ -20,7 +20,7 @@ module RubyDNS def self.run_server(options = {}, &block) server = RubyDNS::Server.new(&block) - options[:listen] ||= [[:udp, "0.0.0.0", 53], [:tcp, "0.0.0.0", 53]] + options[:listen] ||= [[:udp, '0.0.0.0', 53], [:tcp, '0.0.0.0', 53]] EventMachine.run do server.fire(:setup) @@ -70,9 +70,9 @@ module RubyDNS # 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 + if pattern[0] == rule.pattern && + pattern[1] == rule.type && + block_src == rule.block id = rule.id throw :match @@ -81,6 +81,7 @@ module RubyDNS id = generate_id + # FIXME Use block case block.class.name when String @rules << Rule.new(id, pattern, eval(block_src)) @@ -171,7 +172,7 @@ module RubyDNS # New method that generates a unique id for a rule def generate_id begin - id = BeEF::Core::Crypto::secure_token.byteslice(0..6) + 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 } @@ -190,15 +191,15 @@ module RubyDNS 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 From 44622345d0ce5526937158965b455e9bfd8aa7c5 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 3 Jun 2013 17:55:58 -0400 Subject: [PATCH 35/78] s/DNS/Dns/g since that is the BeEF style convention. --- extensions/dns/api.rb | 10 +++++----- extensions/dns/dns.rb | 6 +++--- extensions/dns/extension.rb | 2 +- extensions/dns/model.rb | 2 +- extensions/dns/rest/dns.rb | 12 ++++++------ extensions/dns/ruby/rubydns.rb | 16 ++++++++-------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index 231ad993d..f2d0e7e28 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -5,19 +5,19 @@ # module BeEF module Extension -module DNS +module Dns module API module NameserverHandler BeEF::API::Registrar.instance.register( - BeEF::Extension::DNS::API::NameserverHandler, + BeEF::Extension::Dns::API::NameserverHandler, BeEF::API::Server, 'pre_http_start' ) BeEF::API::Registrar.instance.register( - BeEF::Extension::DNS::API::NameserverHandler, + BeEF::Extension::Dns::API::NameserverHandler, BeEF::API::Server, 'mount_handler' ) @@ -30,7 +30,7 @@ module API port = config.get('beef.extension.dns.port') Thread.new do - dns = BeEF::Extension::DNS::Server.instance + dns = BeEF::Extension::Dns::Server.instance dns.run_server(address, port) end @@ -39,7 +39,7 @@ module API # Mounts handler for processing RESTful API calls def self.mount_handler(beef_server) - 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/dns.rb b/extensions/dns/dns.rb index 7fa0f6ab9..78d7259db 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -5,7 +5,7 @@ # module BeEF module Extension -module DNS +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 @@ -38,7 +38,7 @@ module DNS EventMachine.next_tick do RubyDNS.run_server(:listen => [[:udp, address, port]]) do server = self - BeEF::Extension::DNS::Server.instance.instance_eval { @server = server } + BeEF::Extension::Dns::Server.instance.instance_eval { @server = server } # Pass unmatched queries upstream to root nameservers otherwise do |transaction| @@ -54,7 +54,7 @@ module DNS # # @example Adds an A record for foobar.com with the value 1.2.3.4 # - # dns = BeEF::Extension::DNS::Server.instance + # 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') diff --git a/extensions/dns/extension.rb b/extensions/dns/extension.rb index c725fd1d5..702f602f7 100644 --- a/extensions/dns/extension.rb +++ b/extensions/dns/extension.rb @@ -5,7 +5,7 @@ # module BeEF module Extension -module DNS +module Dns extend BeEF::API::Extension diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index 7afea0c24..ee6a4e291 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -6,7 +6,7 @@ module BeEF module Core module Models -module DNS +module Dns class Rule diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index cec32c7d7..f83568788 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -5,10 +5,10 @@ # module BeEF module Extension -module DNS +module Dns # This class handles the routing of RESTful API requests that query BeEF's DNS server - class DNSRest < BeEF::Core::Router::Router + class DnsRest < BeEF::Core::Router::Router # Filters out bad requests before performing any routing before do @@ -27,7 +27,7 @@ module DNS # Returns the entire current DNS ruleset get '/rules' do result = {} - result[:rules] = BeEF::Extension::DNS::Server.instance.get_ruleset + result[:rules] = BeEF::Extension::Dns::Server.instance.get_ruleset result.to_json end @@ -40,7 +40,7 @@ module DNS raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' end - result = BeEF::Extension::DNS::Server.instance.get_rule(id) + result = BeEF::Extension::Dns::Server.instance.get_rule(id) result.to_json rescue InvalidJsonError => e print_error e.message @@ -87,7 +87,7 @@ module DNS block_src = format_response(type, response) # Bypass #add_rule so that 'block_src' can be passed as a String - BeEF::Extension::DNS::Server.instance.instance_eval do + BeEF::Extension::Dns::Server.instance.instance_eval do id = @server.match(pattern, type_obj, block_src) end @@ -114,7 +114,7 @@ module DNS raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' end - BeEF::Extension::DNS::Server.instance.remove_rule(id) + BeEF::Extension::Dns::Server.instance.remove_rule(id) rescue InvalidJsonError => e print_error e.message halt 400 diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 309e1cfb1..dc3c02047 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -11,7 +11,7 @@ # 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. +# BeEF::Extension::Dns::Server is simply a small wrapper around it. # # @see http://rubydoc.info/gems/rubydns/frames module RubyDNS @@ -69,7 +69,7 @@ module RubyDNS end # Break out and return id if rule is already present - BeEF::Core::Models::DNS::Rule.each do |rule| + BeEF::Core::Models::Dns::Rule.each do |rule| if pattern[0] == rule.pattern && pattern[1] == rule.type && block_src == rule.block @@ -89,7 +89,7 @@ module RubyDNS @rules << Rule.new(id, pattern, block) end - BeEF::Core::Models::DNS::Rule.create( + BeEF::Core::Models::Dns::Rule.create( :id => id, :pattern => pattern[0], :type => pattern[1], @@ -114,7 +114,7 @@ module RubyDNS @rules.delete_if { |rule| rule.id == id } begin - BeEF::Core::Models::DNS::Rule.get!(id).destroy + BeEF::Core::Models::Dns::Rule.get!(id).destroy rescue DataMapper::ObjectNotFoundError => e @logger.error(e.message) end @@ -122,7 +122,7 @@ module RubyDNS # New method that loads all rules from the database at server startup def load_rules - BeEF::Core::Models::DNS::Rule.each do |rule| + BeEF::Core::Models::Dns::Rule.each do |rule| id = rule.id pattern = [rule.pattern, rule.type] block = eval rule.block @@ -135,7 +135,7 @@ module RubyDNS def get_ruleset result = [] - BeEF::Core::Models::DNS::Rule.each do |rule| + BeEF::Core::Models::Dns::Rule.each do |rule| element = {} element[:id] = rule.id @@ -154,7 +154,7 @@ module RubyDNS result = {} begin - rule = BeEF::Core::Models::DNS::Rule.get!(id) + rule = BeEF::Core::Models::Dns::Rule.get!(id) result[:id] = rule.id result[:pattern] = rule.pattern @@ -175,7 +175,7 @@ module RubyDNS 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 } + BeEF::Core::Models::Dns::Rule.each { |rule| throw StandardError if id == rule.id } rescue StandardError retry end From 0af4029915904f40b2f8bee5aaee93a2019bde06 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 3 Jun 2013 21:42:34 -0400 Subject: [PATCH 36/78] Added placeholders necessary to start DNS unit tests. Currently does nothing but assert(true). --- test/unit/extensions/tc_dns.rb | 19 +++++++++++++++++++ test/unit/ts_unit.rb | 2 ++ 2 files changed, 21 insertions(+) create mode 100644 test/unit/extensions/tc_dns.rb diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb new file mode 100644 index 000000000..63d7c9b81 --- /dev/null +++ b/test/unit/extensions/tc_dns.rb @@ -0,0 +1,19 @@ +# +# 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 'test/unit' + +class TC_Dns < Test::Unit::TestCase + + def setup + $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '.')) + $root_dir = File.expand_path('../../../../', __FILE__) + end + + def test_dns + assert(true) + end + +end diff --git a/test/unit/ts_unit.rb b/test/unit/ts_unit.rb index f64dee109..fc652f21d 100644 --- a/test/unit/ts_unit.rb +++ b/test/unit/ts_unit.rb @@ -27,6 +27,7 @@ require './extensions/tc_hooks' require './extensions/tc_proxy' require './extensions/tc_requester' require './extensions/tc_event_logger' +require './extensions/tc_dns' require './tc_grep' require './tc_filesystem' @@ -55,6 +56,7 @@ class TS_BeefTests suite << TC_EventLogger.suite suite << TC_Hooks.suite suite << TC_Redirector.suite + suite << TC_Dns.suite return suite end From fce4c9196da69dd3425edc39152359c053cf690c Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 3 Jun 2013 21:53:06 -0400 Subject: [PATCH 37/78] Modified grep test case to allow #eval use in DNS extension. Using #eval is necessary for normal functioning and is now used in a safe manner. --- test/unit/tc_grep.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/tc_grep.rb b/test/unit/tc_grep.rb index accc2e29a..df17f7c0d 100644 --- a/test/unit/tc_grep.rb +++ b/test/unit/tc_grep.rb @@ -12,12 +12,13 @@ class TC_Grep < Test::Unit::TestCase 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| assert(false, "Illegal use of 'eval' in framework: " + path + ':' + line) end end end - end end From 2f5133e11a4573332c071325355e036f1feb2a63 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 5 Jun 2013 15:56:33 -0400 Subject: [PATCH 38/78] Changed GET handlers to return recently fixed rule data. Also wrapped all handlers in a begin/end block that catches internal StandardError exceptions. --- extensions/dns/dns.rb | 2 +- extensions/dns/rest/dns.rb | 17 ++++++++++++++--- extensions/dns/ruby/rubydns.rb | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 78d7259db..b64b86190 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -93,7 +93,7 @@ module Dns # * :id # * :pattern # * :type - # * :block + # * :response # # @return [Array] DNS ruleset (empty if no rules are currently loaded) def get_ruleset diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index f83568788..5fde2d05f 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -26,9 +26,14 @@ module Dns # Returns the entire current DNS ruleset get '/rules' do - result = {} - result[:rules] = BeEF::Extension::Dns::Server.instance.get_ruleset - result.to_json + begin + result = {} + result[:rules] = BeEF::Extension::Dns::Server.instance.get_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 @@ -45,6 +50,9 @@ module Dns rescue InvalidJsonError => 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 @@ -118,6 +126,9 @@ module Dns rescue InvalidJsonError => 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 diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index dc3c02047..5c98dc785 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -140,8 +140,8 @@ module RubyDNS element[:id] = rule.id element[:pattern] = rule.pattern - element[:type] = rule.type - element[:block] = rule.block + element[:type] = rule.type.to_s.split('::')[-1] + element[:response] = parse_response(rule.block) result << element end @@ -158,8 +158,8 @@ module RubyDNS result[:id] = rule.id result[:pattern] = rule.pattern - result[:type] = rule.type - result[:block] = rule.block + result[:type] = rule.type.to_s.split('::')[-1] + result[:response] = parse_response(rule.block) rescue DataMapper::ObjectNotFoundError => e @logger.error(e.message) end @@ -183,6 +183,32 @@ module RubyDNS id end + # New method that parses response callback and returns RDATA as an array + def parse_response(block) + # Extract response arguments into an array + args = /(?<=respond!\().*(?=\))/.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 + 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 From e56494d4860a57af74854cb31cc93b239f386349 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 5 Jun 2013 16:30:24 -0400 Subject: [PATCH 39/78] Renamed /rules GET route to /ruleset for the sake of consistency. Also added new "count" key to result that lists the number of rules. --- extensions/dns/rest/dns.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 5fde2d05f..d603df58e 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -25,10 +25,14 @@ module Dns end # Returns the entire current DNS ruleset - get '/rules' do + get '/ruleset' do begin + ruleset = BeEF::Extension::Dns::Server.instance.get_ruleset + count = ruleset.length + result = {} - result[:rules] = BeEF::Extension::Dns::Server.instance.get_ruleset + result[:count] = count + result[:ruleset] = ruleset result.to_json rescue StandardError => e print_error "Internal error while retrieving DNS ruleset (#{e.message})" From 80ab6650546ebe2a2bec33b0051881fb845dc69b Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 5 Jun 2013 16:56:05 -0400 Subject: [PATCH 40/78] Added new InvalidParamError class for handling bad named parameters. Previously, InvalidJsonError was being used mistakenly for this which is misleading considering no JSON was involved. --- extensions/dns/rest/dns.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index d603df58e..5661e7bf7 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -46,12 +46,12 @@ module Dns id = params[:id] unless BeEF::Filters.alphanums_only?(id) - raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' + raise InvalidParamError, 'Invalid "id" parameter passed to endpoint /api/dns/rule/:id' end result = BeEF::Extension::Dns::Server.instance.get_rule(id) result.to_json - rescue InvalidJsonError => e + rescue InvalidParamError => e print_error e.message halt 400 rescue StandardError => e @@ -123,11 +123,11 @@ module Dns id = params[:id] unless BeEF::Filters.alphanums_only?(id) - raise InvalidJsonError, 'Invalid id passed to endpoint /api/dns/rule/:id' + raise InvalidParamError, 'Invalid "id" parameter passed to endpoint /api/dns/rule/:id' end BeEF::Extension::Dns::Server.instance.remove_rule(id) - rescue InvalidJsonError => e + rescue InvalidParamError => e print_error e.message halt 400 rescue StandardError => e @@ -223,6 +223,17 @@ module Dns 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 From 6c61b39d81ea1181cf46de435d9be8c93f4d9160 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 5 Jun 2013 17:09:09 -0400 Subject: [PATCH 41/78] Changed 401 status to 403 in filter for non-permitted IP's. 403 Forbidden is more appropriate since 401 Unauthorized only indicates that authentication is needed. In the case of a bad IP, authentication will make no difference which is exactly what 403 is meant for. --- extensions/dns/rest/dns.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 5661e7bf7..251dae08a 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -15,8 +15,8 @@ module Dns config = BeEF::Core::Configuration.instance # Require a valid API token from a valid IP address - error 401 unless params[:token] == config.get('beef.api_token') - halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip) + 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', From 89a5d6fdbbbfeba1608152342006eaaf26a5371b Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 5 Jun 2013 18:20:48 -0400 Subject: [PATCH 42/78] Modified #remove_rule to return a boolean value. This is will soon allow the DELETE handler to indicate success or failure. --- extensions/dns/dns.rb | 3 +++ extensions/dns/ruby/rubydns.rb | 11 ++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index b64b86190..7304d8621 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -79,6 +79,9 @@ module Dns # 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 do diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 5c98dc785..924bbd0ae 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -44,7 +44,6 @@ module RubyDNS class Rule - # XXX Can this be removed? attr_accessor :id # Now uses an 'id' parameter to uniquely identify rules @@ -109,15 +108,13 @@ module RubyDNS id end - # New method that removes a rule given its id + # New method that removes a rule given its id and returns boolean result def remove_rule(id) @rules.delete_if { |rule| rule.id == id } - begin - BeEF::Core::Models::Dns::Rule.get!(id).destroy - rescue DataMapper::ObjectNotFoundError => e - @logger.error(e.message) - end + rule = BeEF::Core::Models::Dns::Rule.get(id) + + rule != nil ? rule.destroy : false end # New method that loads all rules from the database at server startup From fc6f0aface0189e64410a8a4115af4555aca6f1e Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 5 Jun 2013 18:29:18 -0400 Subject: [PATCH 43/78] Changed DELETE handler to return JSON "success" key. Prior to this, nothing was returned. This will allow users to determine whether or not a rule was removed as expected. --- extensions/dns/rest/dns.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 251dae08a..91528e803 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -126,7 +126,9 @@ module Dns raise InvalidParamError, 'Invalid "id" parameter passed to endpoint /api/dns/rule/:id' end - BeEF::Extension::Dns::Server.instance.remove_rule(id) + result = {} + result[:success] = BeEF::Extension::Dns::Server.instance.remove_rule(id) + result.to_json rescue InvalidParamError => e print_error e.message halt 400 From 09ec09601e0cf5ab065f97d4c4d4d8cf028b017e Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 5 Jun 2013 18:33:08 -0400 Subject: [PATCH 44/78] Changed hash key syntax from previous commit. Besides being consistent, Sinatra actually requires the string syntax. --- extensions/dns/rest/dns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 91528e803..94b64ab1d 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -127,7 +127,7 @@ module Dns end result = {} - result[:success] = BeEF::Extension::Dns::Server.instance.remove_rule(id) + result['success'] = BeEF::Extension::Dns::Server.instance.remove_rule(id) result.to_json rescue InvalidParamError => e print_error e.message From 6901581ae73c18990cc615c080ce9d50d9775f52 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 6 Jun 2013 22:59:54 -0400 Subject: [PATCH 45/78] Moved #format_response call to before when RR type is evaled. Since #format_response throws an exception for unknown RR types, calling it first will ensure bad Resolv::DNS::Resource names will never be evaled. --- extensions/dns/rest/dns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 94b64ab1d..380b5a08f 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -95,8 +95,8 @@ module Dns id = '' - type_obj = eval "Resolv::DNS::Resource::IN::#{type}" 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 From 1f37ceec9fcd1dbb1c29957b748a742e0b15bf96 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 6 Jun 2013 23:16:40 -0400 Subject: [PATCH 46/78] Began first integration tests for DNS RESTful API interface. First test is for POST /api/dns/rule handler. --- test/common/test_constants.rb | 3 +- test/integration/tc_dns_rest.rb | 49 ++++++++++++++++++++++++++++++ test/integration/ts_integration.rb | 2 ++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/integration/tc_dns_rest.rb diff --git a/test/common/test_constants.rb b/test/common/test_constants.rb index 856161249..b28f315f6 100644 --- a/test/common/test_constants.rb +++ b/test/common/test_constants.rb @@ -19,4 +19,5 @@ BEEF_PASSWD = "beef" RESTAPI_HOOKS = "http://" + ATTACK_DOMAIN + ":3000/api/hooks" RESTAPI_LOGS = "http://" + ATTACK_DOMAIN + ":3000/api/logs" RESTAPI_MODULES = "http://" + ATTACK_DOMAIN + ":3000/api/modules" -RESTAPI_ADMIN = "http://" + ATTACK_DOMAIN + ":3000/api/admin" \ No newline at end of file +RESTAPI_DNS = "http://" + ATTACK_DOMAIN + ":3000/api/dns" +RESTAPI_ADMIN = "http://" + ATTACK_DOMAIN + ":3000/api/admin" diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb new file mode 100644 index 000000000..3e5eda166 --- /dev/null +++ b/test/integration/tc_dns_rest.rb @@ -0,0 +1,49 @@ +# +# 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 'test/unit' +require 'rest_client' +require 'json' +require '../common/test_constants' + +class TC_DnsRest < Test::Unit::TestCase + + class << self + + def startup + json = {:username => BEEF_USER, :password => BEEF_PASSWD}.to_json + @@headers = {:content_type => :json, :accept => :json} + + response = RestClient.post("#{RESTAPI_ADMIN}/login", + json, + @@headers) + + result = JSON.parse(response.body) + @@token = result['token'] + end + + end + + def test_1_add_rule + pattern = 'foo.bar' + type = 'A' + dns_response = ['1.2.3.4'] + + json = {:pattern => pattern, :type => type, :response => dns_response}.to_json + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + json, + @@headers) + + assert_not_nil(rest_response.body) + assert_equal(200, rest_response.code) + + result = JSON.parse(rest_response.body) + + assert(result['success']) + assert(result['id']) + end + +end diff --git a/test/integration/ts_integration.rb b/test/integration/ts_integration.rb index c148a9c9d..d25ef06c7 100644 --- a/test/integration/ts_integration.rb +++ b/test/integration/ts_integration.rb @@ -16,6 +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 class TS_BeefIntegrationTests def self.suite @@ -25,6 +26,7 @@ class TS_BeefIntegrationTests suite << TC_login.suite suite << TC_DebugModules.suite suite << TC_Jools.suite + suite << TC_DnsRest.suite return suite end From cfa9177af1f8291536f5f50a9a6da7435bd02e6f Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Thu, 6 Jun 2013 23:58:12 -0400 Subject: [PATCH 47/78] Added 4 new tests for bad POST /api/dns/rule requests. --- test/integration/tc_dns_rest.rb | 46 ++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 3e5eda166..898d2f16a 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -26,7 +26,7 @@ class TC_DnsRest < Test::Unit::TestCase end - def test_1_add_rule + def test_1_add_rule_good pattern = 'foo.bar' type = 'A' dns_response = ['1.2.3.4'] @@ -46,4 +46,48 @@ class TC_DnsRest < Test::Unit::TestCase assert(result['id']) end + def test_2_add_rule_bad + pattern = '' + type = 'A' + dns_response = ['1.1.1.1'] + + hash = {:pattern => pattern, :type => type, :response => dns_response} + + # Test that an empty "pattern" key returns 400 + assert_raise RestClient::BadRequest do + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + end + + hash['pattern'] = 'foo.bar.baz' + hash['type'] = '' + + # Test that an empty "type" key returns 400 + assert_raise RestClient::BadRequest do + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + end + + hash['type'] = 'A' + hash['response'] = [] + + # Test that an empty "response" key returns 400 + assert_raise RestClient::BadRequest do + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + end + + hash['response'] = 42 + + # Test that a non-array "response" key returns 400 + assert_raise RestClient::BadRequest do + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + end + end + end From 019ec2f6ed7bba7f581d1096c5bbb1c406ea2538 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 7 Jun 2013 00:05:15 -0400 Subject: [PATCH 48/78] Added new test for attempting to add an existing rule. --- test/integration/tc_dns_rest.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 898d2f16a..8e74f72ae 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -37,6 +37,7 @@ class TC_DnsRest < Test::Unit::TestCase json, @@headers) + # Test that adding a new rule works properly assert_not_nil(rest_response.body) assert_equal(200, rest_response.code) @@ -44,6 +45,22 @@ class TC_DnsRest < Test::Unit::TestCase assert(result['success']) assert(result['id']) + + first_id = result['id'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + json, + @@headers) + + # Test that adding an existing rule returns its id + assert_not_nil(rest_response.body) + assert_equal(200, rest_response.code) + + result = JSON.parse(rest_response.body) + second_id = result['id'] + + assert(result['success']) + assert_equal(first_id, second_id) end def test_2_add_rule_bad From eccbdd6958b5a9c3114e6d7e1d34b83329c41965 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 7 Jun 2013 18:32:29 -0400 Subject: [PATCH 49/78] Added tests for AAAA, CNAME, HINFO, MINFO, and MX RR types. Also fixed #format_response to properly format MS records. --- extensions/dns/rest/dns.rb | 2 +- test/integration/tc_dns_rest.rb | 128 ++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 380b5a08f..05e076140 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -170,7 +170,7 @@ module Dns data when 'MX' data = { :preference => rdata[0], :exchange => rdata[1] } - sprintf "'%d', Resolv::DNS::Name.create('%s')", data + sprintf "%d, Resolv::DNS::Name.create('%s')", data when 'NS' data = { :nsdname => rdata[0] } sprintf "Resolv::DNS::Name.create('%s')", data diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 8e74f72ae..c17b1c8cb 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -107,4 +107,132 @@ class TC_DnsRest < Test::Unit::TestCase end end + # OPTIMIZE: Can this be refactored somehow? + # TODO: Use BeEF::Core::Configuration to get address and port values. + + # Tests each supported RR type + def test_3_add_rule_types + pattern = 'be.ef' + type = 'AAAA' + dns_response = ['2001:db8:ac10:fe01::'] + + hash = {'pattern' => pattern, 'type' => type, 'response' => dns_response} + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test AAAA type + regex = %r{ + ^be\.ef\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + #{hash['response'][0]}$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'CNAME' + hash['response'] = ['fe.eb.'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test CNAME type + regex = %r{ + ^be\.ef\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + #{hash['response'][0]}$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'HINFO' + hash['response'] = ['M6800', 'VMS'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test HINFO type + regex = %r{ + ^be\.ef\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + "#{hash['response'][0]}"\s+ + "#{hash['response'][1]}"$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'MINFO' + hash['response'] = ['rmail.be.ef.', 'email.be.ef.'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test MINFO type + regex = %r{ + ^be\.ef\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + #{hash['response'][0]}\s+ + #{hash['response'][1]}$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'MX' + hash['response'] = [10, 'mail.be.ef.'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test MX type + regex = %r{ + ^be\.ef\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + #{hash['response'][0]}\s+ + #{hash['response'][1]}$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + end + + def check_response(response) + assert_not_nil(response.body) + assert_equal(200, response.code) + + result = JSON.parse(response.body) + + assert(result['success']) + assert(result['id']) + end + end From 68e56fa8c017065ccd0b721aeda6068cb36a71ad Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 7 Jun 2013 23:03:10 -0400 Subject: [PATCH 50/78] Added tests for NS, PTR, SOA, TXT, WKS, and invalid RR types. All RR's are now tested. Though the tests are yet to be optimized. --- test/integration/tc_dns_rest.rb | 132 ++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index c17b1c8cb..d3216f34e 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -126,7 +126,7 @@ class TC_DnsRest < Test::Unit::TestCase # Test AAAA type regex = %r{ - ^be\.ef\.\t+ + ^#{hash['pattern']}\.\t+ \d+\t+ IN\t+ #{hash['type']}\t+ @@ -147,7 +147,7 @@ class TC_DnsRest < Test::Unit::TestCase # Test CNAME type regex = %r{ - ^be\.ef\.\t+ + ^#{hash['pattern']}\.\t+ \d+\t+ IN\t+ #{hash['type']}\t+ @@ -168,7 +168,7 @@ class TC_DnsRest < Test::Unit::TestCase # Test HINFO type regex = %r{ - ^be\.ef\.\t+ + ^#{hash['pattern']}\.\t+ \d+\t+ IN\t+ #{hash['type']}\t+ @@ -190,7 +190,7 @@ class TC_DnsRest < Test::Unit::TestCase # Test MINFO type regex = %r{ - ^be\.ef\.\t+ + ^#{hash['pattern']}\.\t+ \d+\t+ IN\t+ #{hash['type']}\t+ @@ -212,7 +212,7 @@ class TC_DnsRest < Test::Unit::TestCase # Test MX type regex = %r{ - ^be\.ef\.\t+ + ^#{hash['pattern']}\.\t+ \d+\t+ IN\t+ #{hash['type']}\t+ @@ -223,6 +223,128 @@ class TC_DnsRest < Test::Unit::TestCase dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` assert_match(regex, dig_output) + hash['type'] = 'NS' + hash['response'] = ['ns.be.ef.'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test NS type + regex = %r{ + ^#{hash['pattern']}\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + #{hash['response'][0]}$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'PTR' + hash['response'] = ['4.3.2.1.in-addr.arpa.'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test PTR type + regex = %r{ + ^#{hash['pattern']}\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + #{hash['response'][0]}$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'SOA' + hash['response'] = [ + "ns.#{hash['pattern']}.", + "mail.#{hash['pattern']}.", + 2012031500, + 10800, + 3600, + 604800, + 3600 + ] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test SOA type + regex = %r{ + ^#{hash['pattern']}\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + .* + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'TXT' + hash['response'] = ['When in doubt, use brute force!'] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test TXT type + regex = %r{ + ^#{hash['pattern']}\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + "#{hash['response'][0].gsub!(' ', '\s')}"$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'WKS' + hash['response'] = ['9.9.9.9', 6, 0] + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + + check_response(rest_response) + + # Test WKS type + regex = %r{ + ^#{hash['pattern']}\.\t+ + \d+\t+ + IN\t+ + #{hash['type']}\t+ + #{hash['response'][0]}\s + 0\s5\s6$ + }x + + dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` + assert_match(regex, dig_output) + + hash['type'] = 'BeEF' + + # Test invalid RR + assert_raise RestClient::BadRequest do + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + hash.to_json, + @@headers) + end end def check_response(response) From e527f1ae09539667cc0eb710d59bf88d686749c4 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 7 Jun 2013 23:09:07 -0400 Subject: [PATCH 51/78] Refactored redundant code in #test_1_add_rule_good. --- test/integration/tc_dns_rest.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index d3216f34e..885d5044d 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -38,14 +38,9 @@ class TC_DnsRest < Test::Unit::TestCase @@headers) # Test that adding a new rule works properly - assert_not_nil(rest_response.body) - assert_equal(200, rest_response.code) + check_response(rest_response) result = JSON.parse(rest_response.body) - - assert(result['success']) - assert(result['id']) - first_id = result['id'] rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", @@ -53,13 +48,11 @@ class TC_DnsRest < Test::Unit::TestCase @@headers) # Test that adding an existing rule returns its id - assert_not_nil(rest_response.body) - assert_equal(200, rest_response.code) + check_response(rest_response) result = JSON.parse(rest_response.body) second_id = result['id'] - assert(result['success']) assert_equal(first_id, second_id) end @@ -339,7 +332,7 @@ class TC_DnsRest < Test::Unit::TestCase hash['type'] = 'BeEF' - # Test invalid RR + # Test that an invalid RR returns 400 assert_raise RestClient::BadRequest do rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", hash.to_json, From b9d64f0b89cb4af744f99c0cb8a92210fb8b743a Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 7 Jun 2013 23:56:19 -0400 Subject: [PATCH 52/78] Significantly refactored code in #test_3_add_rule_types. --- test/integration/tc_dns_rest.rb | 263 +++++++++++++------------------- 1 file changed, 110 insertions(+), 153 deletions(-) diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 885d5044d..5d97e5574 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -38,7 +38,7 @@ class TC_DnsRest < Test::Unit::TestCase @@headers) # Test that adding a new rule works properly - check_response(rest_response) + check_rest_response(rest_response) result = JSON.parse(rest_response.body) first_id = result['id'] @@ -48,7 +48,7 @@ class TC_DnsRest < Test::Unit::TestCase @@headers) # Test that adding an existing rule returns its id - check_response(rest_response) + check_rest_response(rest_response) result = JSON.parse(rest_response.body) second_id = result['id'] @@ -100,168 +100,124 @@ class TC_DnsRest < Test::Unit::TestCase end end - # OPTIMIZE: Can this be refactored somehow? - # TODO: Use BeEF::Core::Configuration to get address and port values. - # Tests each supported RR type def test_3_add_rule_types pattern = 'be.ef' type = 'AAAA' - dns_response = ['2001:db8:ac10:fe01::'] - - hash = {'pattern' => pattern, 'type' => type, 'response' => dns_response} - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + response = ['2001:db8:ac10:fe01::'] # Test AAAA type + rule = {'pattern' => pattern, 'type' => type, 'response' => response} + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - #{hash['response'][0]}$ + #{rule['type']}\t+ + #{rule['response'][0]}$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'CNAME' - hash['response'] = ['fe.eb.'] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test CNAME type + rule['type'] = 'CNAME' + rule['response'] = ['fe.eb.'] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - #{hash['response'][0]}$ + #{rule['type']}\t+ + #{rule['response'][0]}$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'HINFO' - hash['response'] = ['M6800', 'VMS'] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test HINFO type + rule['type'] = 'HINFO' + rule['response'] = ['M6800', 'VMS'] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - "#{hash['response'][0]}"\s+ - "#{hash['response'][1]}"$ + #{rule['type']}\t+ + "#{rule['response'][0]}"\s+ + "#{rule['response'][1]}"$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'MINFO' - hash['response'] = ['rmail.be.ef.', 'email.be.ef.'] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test MINFO type + rule['type'] = 'MINFO' + rule['response'] = ['rmail.be.ef.', 'email.be.ef.'] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - #{hash['response'][0]}\s+ - #{hash['response'][1]}$ + #{rule['type']}\t+ + #{rule['response'][0]}\s+ + #{rule['response'][1]}$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'MX' - hash['response'] = [10, 'mail.be.ef.'] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test MX type + rule['type'] = 'MX' + rule['response'] = [10, 'mail.be.ef.'] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - #{hash['response'][0]}\s+ - #{hash['response'][1]}$ + #{rule['type']}\t+ + #{rule['response'][0]}\s+ + #{rule['response'][1]}$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'NS' - hash['response'] = ['ns.be.ef.'] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test NS type + rule['type'] = 'NS' + rule['response'] = ['ns.be.ef.'] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - #{hash['response'][0]}$ + #{rule['type']}\t+ + #{rule['response'][0]}$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'PTR' - hash['response'] = ['4.3.2.1.in-addr.arpa.'] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test PTR type + rule['type'] = 'PTR' + rule['response'] = ['4.3.2.1.in-addr.arpa.'] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - #{hash['response'][0]}$ + #{rule['type']}\t+ + #{rule['response'][0]}$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) - hash['type'] = 'SOA' - hash['response'] = [ - "ns.#{hash['pattern']}.", - "mail.#{hash['pattern']}.", + # Test SOA type + rule['type'] = 'SOA' + rule['response'] = [ + "ns.#{rule['pattern']}.", + "mail.#{rule['pattern']}.", 2012031500, 10800, 3600, @@ -269,78 +225,71 @@ class TC_DnsRest < Test::Unit::TestCase 3600 ] - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) - - # Test SOA type regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ + #{rule['type']}\t+ .* }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'TXT' - hash['response'] = ['When in doubt, use brute force!'] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test TXT type + rule['type'] = 'TXT' + rule['response'] = ['b33f_is_s0_l33t'] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - "#{hash['response'][0].gsub!(' ', '\s')}"$ + #{rule['type']}\t+ + "#{rule['response'][0]}"$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'WKS' - hash['response'] = ['9.9.9.9', 6, 0] - - rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, - @@headers) - - check_response(rest_response) + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test WKS type + rule['type'] = 'WKS' + rule['response'] = ['9.9.9.9', 6, 0] + regex = %r{ - ^#{hash['pattern']}\.\t+ + ^#{rule['pattern']}\.\t+ \d+\t+ IN\t+ - #{hash['type']}\t+ - #{hash['response'][0]}\s + #{rule['type']}\t+ + #{rule['response'][0]}\s 0\s5\s6$ }x - dig_output = `dig @localhost -p 5300 -t #{hash['type']} #{hash['pattern']}` - assert_match(regex, dig_output) - - hash['type'] = 'BeEF' + add_rule(rule) + check_dns_response(regex, rule['type'], rule['pattern']) # Test that an invalid RR returns 400 + rule['type'] = 'BeEF' + assert_raise RestClient::BadRequest do rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", - hash.to_json, + rule.to_json, @@headers) end end - def check_response(response) + private + + # Adds a new DNS rule + def add_rule(params) + response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + params.to_json, + @@headers) + + check_rest_response(response) + end + + # Standard assertions for verifying response from RESTful API + def check_rest_response(response) assert_not_nil(response.body) assert_equal(200, response.code) @@ -350,4 +299,12 @@ class TC_DnsRest < Test::Unit::TestCase assert(result['id']) end + # TODO: Use BeEF::Core::Configuration to get address and port values. + + # Compares output of dig command against regex + def check_dns_response(regex, type, pattern) + dig_output = `dig @localhost -p 5300 -t #{type} #{pattern}` + assert_match(regex, dig_output) + end + end From 3b58518cfd65f3b52e3f4b528a8930c47a61ac8d Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sat, 8 Jun 2013 19:04:42 -0400 Subject: [PATCH 53/78] Added tests for GET /api/dns/rule/:id handler. Fixed #parse_response so that these tests pass. --- extensions/dns/ruby/rubydns.rb | 2 +- test/integration/tc_dns_rest.rb | 36 ++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 924bbd0ae..4e2c795bd 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -198,7 +198,7 @@ module RubyDNS arg = (int_test != 0 ? int_test : elem) end - arg.gsub!('"', '') unless arg.is_a?(Integer) + arg.gsub!(/['"]/, '') unless arg.is_a?(Integer) result << arg end diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 5d97e5574..8c749f47a 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -26,6 +26,7 @@ class TC_DnsRest < Test::Unit::TestCase end + # Tests POST /api/dns/rule handler with valid input def test_1_add_rule_good pattern = 'foo.bar' type = 'A' @@ -37,7 +38,6 @@ class TC_DnsRest < Test::Unit::TestCase json, @@headers) - # Test that adding a new rule works properly check_rest_response(rest_response) result = JSON.parse(rest_response.body) @@ -47,7 +47,7 @@ class TC_DnsRest < Test::Unit::TestCase json, @@headers) - # Test that adding an existing rule returns its id + # Verify that adding an existing rule returns its id check_rest_response(rest_response) result = JSON.parse(rest_response.body) @@ -56,6 +56,7 @@ class TC_DnsRest < Test::Unit::TestCase assert_equal(first_id, second_id) end + # Tests POST /api/dns/rule handler with invalid input def test_2_add_rule_bad pattern = '' type = 'A' @@ -100,7 +101,7 @@ class TC_DnsRest < Test::Unit::TestCase end end - # Tests each supported RR type + # Tests POST /api/dns/rule handler with each supported RR type def test_3_add_rule_types pattern = 'be.ef' type = 'AAAA' @@ -277,6 +278,35 @@ class TC_DnsRest < Test::Unit::TestCase end end + # Tests GET /api/dns/rule/:id handler + def test_4_get_rule_good + pattern = 'wheres.the.beef' + type = 'A' + dns_response = ['4.2.4.2'] + + json = {:pattern => pattern, :type => type, :response => dns_response}.to_json + + rest_response = RestClient.post("#{RESTAPI_DNS}/rule?token=#{@@token}", + json, + @@headers) + + check_rest_response(rest_response) + result = JSON.parse(rest_response.body) + id = result['id'] + + rest_response = RestClient.get("#{RESTAPI_DNS}/rule/#{id}", :params => {:token => @@token}) + + assert_not_nil(rest_response.body) + assert_equal(200, rest_response.code) + + result = JSON.parse(rest_response.body) + + assert_equal(id, result['id']) + assert_equal(pattern, result['pattern']) + assert_equal(type, result['type']) + assert_equal(dns_response, result['response']) + end + private # Adds a new DNS rule From e775748603f9ae8ba8db9c66882b7b070cae6e06 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sat, 8 Jun 2013 21:58:28 -0400 Subject: [PATCH 54/78] Added more tests for GET /api/dns/rule/:id with invalid input. Also changed handler to return 404 when rule isn't found. --- extensions/dns/rest/dns.rb | 2 ++ test/integration/tc_dns_rest.rb | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/extensions/dns/rest/dns.rb b/extensions/dns/rest/dns.rb index 05e076140..754277df7 100644 --- a/extensions/dns/rest/dns.rb +++ b/extensions/dns/rest/dns.rb @@ -50,6 +50,8 @@ module Dns 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 diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 8c749f47a..052d050b2 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -278,7 +278,7 @@ class TC_DnsRest < Test::Unit::TestCase end end - # Tests GET /api/dns/rule/:id handler + # Tests GET /api/dns/rule/:id handler with valid input def test_4_get_rule_good pattern = 'wheres.the.beef' type = 'A' @@ -307,6 +307,21 @@ class TC_DnsRest < Test::Unit::TestCase assert_equal(dns_response, result['response']) end + # Tests GET /api/dns/rule/:id handler with invalid input + def test_4_get_rule_bad + id = 42 + + assert_raise RestClient::ResourceNotFound do + response = RestClient.get("#{RESTAPI_DNS}/rule/#{id}", :params => {:token => @@token}) + end + + id = '(*_*)' + + assert_raise RestClient::BadRequest do + RestClient.get("#{RESTAPI_DNS}/rule/#{id}", :params => {:token => @@token}) + end + end + private # Adds a new DNS rule From 9e1ec69e40e434ff21b9f17a90c01adf3665ca1b Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sat, 8 Jun 2013 22:44:51 -0400 Subject: [PATCH 55/78] Added tests for GET /api/dns/ruleset handler. --- test/integration/tc_dns_rest.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 052d050b2..9919cd6ea 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -322,6 +322,24 @@ class TC_DnsRest < Test::Unit::TestCase end end + # Tests GET /api/dns/ruleset handler + def test_4_get_ruleset + rest_response = RestClient.get("#{RESTAPI_DNS}/ruleset", :params => {:token => @@token}) + + assert_not_nil(rest_response.body) + assert_equal(200, rest_response.code) + + result = JSON.parse(rest_response.body) + assert_equal(15, result['count']) + + result['ruleset'].each do |rule| + assert(rule['id']) + assert(rule['pattern']) + assert(rule['type']) + assert(rule['response'].length != 0) + end + end + private # Adds a new DNS rule From d2ac9e0f7aee15b98ef6106380ed7c8d2de65f13 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sat, 8 Jun 2013 23:25:23 -0400 Subject: [PATCH 56/78] Included broken DNS unit tests so others can help debug. Temporary 'dns' and 'dns_rest' Rake tasks make it easier to run tests. --- Rakefile | 16 ++++++++++++++++ test/integration/ts_dns_rest.rb | 20 ++++++++++++++++++++ test/unit/extensions/tc_dns.rb | 33 ++++++++++++++++++++++++++++----- test/unit/ts_unit_dns.rb | 20 ++++++++++++++++++++ 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 test/integration/ts_dns_rest.rb create mode 100644 test/unit/ts_unit_dns.rb diff --git a/Rakefile b/Rakefile index e40188318..d27412b0a 100644 --- a/Rakefile +++ b/Rakefile @@ -32,6 +32,22 @@ task :integration => ["install"] do Rake::Task['beef_stop'].invoke end +# TODO: Remove task before comitting +desc "Run DNS integration tests" +task :dns_rest do + Rake::Task['beef_start'].invoke + sh "cd test/integration; ruby -W0 ts_dns_rest.rb" + Rake::Task['beef_stop'].invoke +end + +# TODO: Remove task before comitting +desc "Run DNS unit tests" +task :dns do + Rake::Task['beef_start'].invoke + sh "cd test/unit; ruby -W0 ts_unit_dns.rb" + Rake::Task['beef_stop'].invoke +end + desc "Run integration unit tests" task :unit => ["install"] do sh "cd test/unit;ruby -W0 ts_unit.rb" diff --git a/test/integration/ts_dns_rest.rb b/test/integration/ts_dns_rest.rb new file mode 100644 index 000000000..301d30bba --- /dev/null +++ b/test/integration/ts_dns_rest.rb @@ -0,0 +1,20 @@ +# +# 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/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 63d7c9b81..d9a2ab64d 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -4,16 +4,39 @@ # See the file 'doc/COPYING' for copying permission # require 'test/unit' +require 'resolv' class TC_Dns < Test::Unit::TestCase - def setup - $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '.')) - $root_dir = File.expand_path('../../../../', __FILE__) + class << self + + def startup + $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '../../../')) + + require 'core/loader' + require 'extensions/dns/extension' + + config = BeEF::Core::Configuration.instance + + @@address = config.get('beef.extension.dns.address') + @@port = config.get('beef.extension.dns.port') + + Thread.new do + dns = BeEF::Extension::Dns::Server.instance + dns.run_server(@@address, @@port) + end + end + end - def test_dns - assert(true) + def test_add_rule + dns = BeEF::Extension::Dns::Server.instance + + id = dns.add_rule('foo.bar', Resolv::DNS::Resource::IN::A) do |transaction| + transaction.respond!('1.2.3.4') + end + + assert_not_nil(id) end end diff --git a/test/unit/ts_unit_dns.rb b/test/unit/ts_unit_dns.rb new file mode 100644 index 000000000..986eb3f17 --- /dev/null +++ b/test/unit/ts_unit_dns.rb @@ -0,0 +1,20 @@ +# +# 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 './extensions/tc_dns' + +class TS_BeefDnsTests + + def self.suite + suite = Test::Unit::TestSuite.new(name="BeEF DNS Unit Tests") + suite << TC_Dns.suite + + return suite + end + +end + +Test::Unit::UI::Console::TestRunner.run(TS_BeefDnsTests) From d45bff3a5914f2117befdbc09fa8985faeea1860 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 10 Jun 2013 18:11:36 -0400 Subject: [PATCH 57/78] Improved #check_dns_response to use config file for address/port. --- test/integration/tc_dns_rest.rb | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/integration/tc_dns_rest.rb b/test/integration/tc_dns_rest.rb index 9919cd6ea..b6921ebf5 100644 --- a/test/integration/tc_dns_rest.rb +++ b/test/integration/tc_dns_rest.rb @@ -22,6 +22,20 @@ class TC_DnsRest < Test::Unit::TestCase result = JSON.parse(response.body) @@token = result['token'] + + $root_dir = '../../' + $:.unshift($root_dir) + + require 'core/loader' + + BeEF::Core::Configuration.new(File.join($root_dir, 'config.yaml')) + BeEF::Core::Configuration.instance.load_extensions_config + + @@config = BeEF::Core::Configuration.instance + end + + def shutdown + $root_dir = nil end end @@ -362,11 +376,12 @@ class TC_DnsRest < Test::Unit::TestCase assert(result['id']) end - # TODO: Use BeEF::Core::Configuration to get address and port values. - # Compares output of dig command against regex def check_dns_response(regex, type, pattern) - dig_output = `dig @localhost -p 5300 -t #{type} #{pattern}` + address = @@config.get('beef.extension.dns.address') + port = @@config.get('beef.extension.dns.port') + + dig_output = `dig @#{address} -p #{port} -t #{type} #{pattern}` assert_match(regex, dig_output) end From d9f7af27210e9dbd3197eb578f6a699313e688c5 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sat, 22 Jun 2013 03:32:42 -0400 Subject: [PATCH 58/78] Reference point for broken unit tests. NoMethodError and NameError are present. --- test/unit/extensions/tc_dns.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index d9a2ab64d..09ec52cfb 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -11,10 +11,14 @@ class TC_Dns < Test::Unit::TestCase class << self def startup - $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '../../../')) + $root_dir = '../../../' + $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), $root_dir)) require 'core/loader' - require 'extensions/dns/extension' + require 'extensions/dns/extension.rb' + + BeEF::Core::Configuration.new(File.join($root_dir, 'config.yaml')) + BeEF::Core::Configuration.instance.load_extensions_config config = BeEF::Core::Configuration.instance From ebbadba6dd9ac6cfea1a6cc65cdc96e1f8d0d5b3 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Sun, 14 Jul 2013 23:27:21 -0400 Subject: [PATCH 59/78] Improved #run_server to check if EM reactor is already running. Also moved Thread creation to inside #run_server instead of forcing caller to do so. --- extensions/dns/api.rb | 12 ++++----- extensions/dns/dns.rb | 58 +++++++++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/extensions/dns/api.rb b/extensions/dns/api.rb index f2d0e7e28..b5cf4e65d 100644 --- a/extensions/dns/api.rb +++ b/extensions/dns/api.rb @@ -24,15 +24,13 @@ module API # Begins main DNS server run-loop at BeEF startup def self.pre_http_start(http_hook_server) - config = BeEF::Core::Configuration.instance + dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns') - address = config.get('beef.extension.dns.address') - port = config.get('beef.extension.dns.port') + address = dns_config['address'] + port = dns_config['port'] - Thread.new do - dns = BeEF::Extension::Dns::Server.instance - dns.run_server(address, port) - end + dns = BeEF::Extension::Dns::Server.instance + dns.run_server(address, port) print_info "DNS Server: #{address}:#{port}" end diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 7304d8621..2318b16e5 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -27,24 +27,22 @@ module Dns @server = nil end - # Starts the main DNS server run-loop. - # - # @note This method will not return. It is recommended that it be invoked inside a - # separate thread. + # 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) - EventMachine.next_tick do - RubyDNS.run_server(:listen => [[:udp, address, port]]) do - server = self - BeEF::Extension::Dns::Server.instance.instance_eval { @server = server } + @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) - # 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]]) - ) + if EventMachine.reactor_running? + EventMachine.next_tick { run_server_block(address, port) } + else + run_server_block(address, port) end end end @@ -71,9 +69,7 @@ module Dns # @see #remove_rule # @see http://rubydoc.info/gems/rubydns/RubyDNS/Transaction def add_rule(pattern, type, &block) - @lock.synchronize do - return @server.match(pattern, type, block) - end + @lock.synchronize { @server.match(pattern, type, block) } end # Removes the given DNS rule. Any future queries for it will be passed through. @@ -84,9 +80,7 @@ module Dns # # @see #add_rule def remove_rule(id) - @lock.synchronize do - @server.remove_rule(id) - end + @lock.synchronize { @server.remove_rule(id) } end # Returns an AoH representing the entire current DNS ruleset. @@ -100,9 +94,7 @@ module Dns # # @return [Array] DNS ruleset (empty if no rules are currently loaded) def get_ruleset - @lock.synchronize do - @server.get_ruleset - end + @lock.synchronize { @server.get_ruleset } end # Retrieves a specific rule given its id @@ -111,8 +103,26 @@ module Dns # # @return [Hash] hash representation of rule def get_rule(id) - @lock.synchronize do - @server.get_rule(id) + @lock.synchronize { @server.get_rule(id) } + 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 From 123c3cdc04603faa1e0e58422311820436d7e598 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 02:00:12 -0400 Subject: [PATCH 60/78] FIXED UNIT TEST ISSUES!!! \(^o^)/ Load path and configuration setup belong in #startup along with any required files. DataMapper adapter connections go in #setup. That's the secret recipe. --- test/unit/extensions/tc_dns.rb | 35 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 09ec52cfb..a5477d13b 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -8,39 +8,36 @@ require 'resolv' class TC_Dns < Test::Unit::TestCase + IN = Resolv::DNS::Resource::IN + class << self def startup - $root_dir = '../../../' - $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), $root_dir)) + $root_dir = '../../' + $:.unshift(File.expand_path($root_dir)) - require 'core/loader' - require 'extensions/dns/extension.rb' + require 'extensions/dns/extension' BeEF::Core::Configuration.new(File.join($root_dir, 'config.yaml')) - BeEF::Core::Configuration.instance.load_extensions_config - config = BeEF::Core::Configuration.instance + config.load_extensions_config - @@address = config.get('beef.extension.dns.address') - @@port = config.get('beef.extension.dns.port') + @@dns_config = config.get('beef.extension.dns') + end - Thread.new do - dns = BeEF::Extension::Dns::Server.instance - dns.run_server(@@address, @@port) - end + def shutdown + $root_dir = nil end end - def test_add_rule - dns = BeEF::Extension::Dns::Server.instance + def setup + DataMapper.setup(:default, 'sqlite3::memory:') + DataMapper.auto_migrate! + end - id = dns.add_rule('foo.bar', Resolv::DNS::Resource::IN::A) do |transaction| - transaction.respond!('1.2.3.4') - end - - assert_not_nil(id) + def test_nothing + assert(true) end end From 3865aab7ee1250f79cf385a7b0e5402cdd47b7a4 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 02:07:26 -0400 Subject: [PATCH 61/78] Added unit tests for required config.yaml settings. --- test/unit/extensions/tc_dns.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index a5477d13b..9c1164e95 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -36,8 +36,10 @@ class TC_Dns < Test::Unit::TestCase DataMapper.auto_migrate! end - def test_nothing - assert(true) + # Checks for required settings in config file + def test_1_config + assert(@@dns_config.has_key?('address')) + assert(@@dns_config.has_key?('port')) end end From 8270abd2d5cddfeec2ef9588ac9f3190d105c451 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 02:12:31 -0400 Subject: [PATCH 62/78] Added unit tests for Dns::Server public interface. --- test/unit/extensions/tc_dns.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 9c1164e95..5c516f99e 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -42,4 +42,15 @@ class TC_Dns < Test::Unit::TestCase assert(@@dns_config.has_key?('port')) end + # Verifies public interface + def test_2_interface + @@dns = BeEF::Extension::Dns::Server.instance + + assert(@@dns.respond_to?('run_server')) + assert(@@dns.respond_to?('add_rule')) + assert(@@dns.respond_to?('remove_rule')) + assert(@@dns.respond_to?('get_ruleset')) + assert(@@dns.respond_to?('get_rule')) + end + end From d8a8e37029741e776eea39cc0e97be6bc223b454 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 02:39:41 -0400 Subject: [PATCH 63/78] Moved DM adapter setup into first "test" method. Since #setup is called between each test, the database table would otherwise be cleared every time. --- test/unit/extensions/tc_dns.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 5c516f99e..17a4d4c47 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -31,19 +31,19 @@ class TC_Dns < Test::Unit::TestCase end - def setup + def test_1_database DataMapper.setup(:default, 'sqlite3::memory:') DataMapper.auto_migrate! end # Checks for required settings in config file - def test_1_config + def test_2_config assert(@@dns_config.has_key?('address')) assert(@@dns_config.has_key?('port')) end # Verifies public interface - def test_2_interface + def test_3_interface @@dns = BeEF::Extension::Dns::Server.instance assert(@@dns.respond_to?('run_server')) From 1ffa21d62aee850f115deeeb770d19d16c6e663f Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 02:47:37 -0400 Subject: [PATCH 64/78] Added unit tests for #add_rule. These represent the first actual tests for the Dns::Server class. --- test/unit/extensions/tc_dns.rb | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 17a4d4c47..a4a75a4e3 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -31,6 +31,7 @@ class TC_Dns < Test::Unit::TestCase end + # Connects to in-memory database (does not test anything) def test_1_database DataMapper.setup(:default, 'sqlite3::memory:') DataMapper.auto_migrate! @@ -53,4 +54,42 @@ class TC_Dns < Test::Unit::TestCase assert(@@dns.respond_to?('get_rule')) end + # Starts DNS server (does not test anything) + def test_4_run_server + address = @@dns_config['address'] + port = @@dns_config['port'] + + @@dns.run_server(address, port) + sleep(3) + end + + # Tests procedure for adding new DNS rules + def test_5_add_rule + id = nil + same_id = nil + + domain = 'foo.bar' + response = '1.2.3.4' + + # Add a new rule normally + assert_nothing_raised do + id = @@dns.add_rule(domain, IN::A) do |transaction| + transaction.respond!(response) + end + end + + assert_not_nil(id) + assert_equal(7, id.length) + + # Attempt to add an existing rule + assert_nothing_raised do + same_id = @@dns.add_rule(domain, IN::A) do |transaction| + transaction.respond!(response) + end + end + + assert_not_nil(same_id) + assert_equal(same_id, id) + end + end From 9a4fd6cb4c4b22247fe1f39524e5121cfafe1fa7 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 03:02:29 -0400 Subject: [PATCH 65/78] Removed "dns" task in Rakefile since "unit" is fine now. --- Rakefile | 8 -------- test/unit/ts_unit_dns.rb | 20 -------------------- 2 files changed, 28 deletions(-) delete mode 100644 test/unit/ts_unit_dns.rb diff --git a/Rakefile b/Rakefile index d27412b0a..fe92286e7 100644 --- a/Rakefile +++ b/Rakefile @@ -40,14 +40,6 @@ task :dns_rest do Rake::Task['beef_stop'].invoke end -# TODO: Remove task before comitting -desc "Run DNS unit tests" -task :dns do - Rake::Task['beef_start'].invoke - sh "cd test/unit; ruby -W0 ts_unit_dns.rb" - Rake::Task['beef_stop'].invoke -end - desc "Run integration unit tests" task :unit => ["install"] do sh "cd test/unit;ruby -W0 ts_unit.rb" diff --git a/test/unit/ts_unit_dns.rb b/test/unit/ts_unit_dns.rb deleted file mode 100644 index 986eb3f17..000000000 --- a/test/unit/ts_unit_dns.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 './extensions/tc_dns' - -class TS_BeefDnsTests - - def self.suite - suite = Test::Unit::TestSuite.new(name="BeEF DNS Unit Tests") - suite << TC_Dns.suite - - return suite - end - -end - -Test::Unit::UI::Console::TestRunner.run(TS_BeefDnsTests) From 5769615cd58a94caece0a0581b19ba893ae0a78a Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 03:48:01 -0400 Subject: [PATCH 66/78] Added unit tests for #get_rule. Also removed convenience variables from #add_rule tests (domain and response). The "response" key in the hash returned by #get_rule is generated by Sourcify which sourcifies the variable name, not its value. --- test/unit/extensions/tc_dns.rb | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index a4a75a4e3..fe4705d36 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -68,13 +68,12 @@ class TC_Dns < Test::Unit::TestCase id = nil same_id = nil - domain = 'foo.bar' response = '1.2.3.4' # Add a new rule normally assert_nothing_raised do - id = @@dns.add_rule(domain, IN::A) do |transaction| - transaction.respond!(response) + id = @@dns.add_rule('foo.bar', IN::A) do |transaction| + transaction.respond!('1.2.3.4') end end @@ -83,8 +82,8 @@ class TC_Dns < Test::Unit::TestCase # Attempt to add an existing rule assert_nothing_raised do - same_id = @@dns.add_rule(domain, IN::A) do |transaction| - transaction.respond!(response) + same_id = @@dns.add_rule('foo.bar', IN::A) do |transaction| + transaction.respond!('1.2.3.4') end end @@ -92,4 +91,28 @@ class TC_Dns < Test::Unit::TestCase assert_equal(same_id, id) end + # Tests retrieval of DNS rules + def test_6_get_rule + id = @@dns.add_rule('be.ef', IN::A) do |transaction| + transaction.respond!('1.1.1.1') + end + + rule = @@dns.get_rule(id) + + assert(rule.has_key?(:id)) + assert(rule.has_key?(:pattern)) + assert(rule.has_key?(:type)) + assert(rule.has_key?(:response)) + + assert_equal(id, rule[:id]) + assert_equal('be.ef', rule[:pattern]) + assert_equal('A', rule[:type]) + + response = rule[:response] + + assert_equal(Array, response.class) + assert(response.length > 0) + assert_equal('1.1.1.1', response[0]) + end + end From 8d95e6f522664241cba8ea5384da1f8beed3cdbb Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 18:25:33 -0400 Subject: [PATCH 67/78] Changed public interface tests to use #assert_respond_to. This will improve the accuracy of potential error messages. --- test/unit/extensions/tc_dns.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index fe4705d36..5a2087357 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -47,11 +47,11 @@ class TC_Dns < Test::Unit::TestCase def test_3_interface @@dns = BeEF::Extension::Dns::Server.instance - assert(@@dns.respond_to?('run_server')) - assert(@@dns.respond_to?('add_rule')) - assert(@@dns.respond_to?('remove_rule')) - assert(@@dns.respond_to?('get_ruleset')) - assert(@@dns.respond_to?('get_rule')) + assert_respond_to(@@dns, :run_server) + assert_respond_to(@@dns, :add_rule) + assert_respond_to(@@dns, :remove_rule) + assert_respond_to(@@dns, :get_ruleset) + assert_respond_to(@@dns, :get_rule) end # Starts DNS server (does not test anything) From 4d0f58684fc80f036443e2f68c9a83819ceb540d Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 23:07:06 -0400 Subject: [PATCH 68/78] Divided #add_rule tests into separate good and bad tests. Also added unit tests that verify rule id format. --- test/unit/extensions/tc_dns.rb | 39 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 5a2087357..952a132d3 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -63,14 +63,10 @@ class TC_Dns < Test::Unit::TestCase sleep(3) end - # Tests procedure for adding new DNS rules - def test_5_add_rule + # Tests procedure for properly adding new DNS rules + def test_5_add_rule_good id = nil - same_id = nil - response = '1.2.3.4' - - # Add a new rule normally assert_nothing_raised do id = @@dns.add_rule('foo.bar', IN::A) do |transaction| transaction.respond!('1.2.3.4') @@ -78,17 +74,36 @@ class TC_Dns < Test::Unit::TestCase end assert_not_nil(id) - assert_equal(7, id.length) + end + + # Tests that adding existing rules returns current id + def test_6_add_rule_bad + id = nil + same_id = nil - # Attempt to add an existing rule assert_nothing_raised do - same_id = @@dns.add_rule('foo.bar', IN::A) do |transaction| - transaction.respond!('1.2.3.4') + id = @@dns.add_rule('j.random.hacker', IN::A) do |transaction| + transaction.respond!('4.2.4.2') end end - assert_not_nil(same_id) - assert_equal(same_id, id) + assert_nothing_raised do + same_id = @@dns.add_rule('j.random.hacker', IN::A) do |transaction| + transaction.respond!('4.2.4.2') + end + end + + assert_equal(id, same_id) + end + + # Verifies the proper format for rule identifiers + def test_7_id_format + id = @@dns.add_rule('dead.beef', IN::A) do |transaction| + transaction.respond!('2.2.2.2') + end + + assert_equal(7, id.length) + assert_not_nil(id =~ /^\h{7}$/) end # Tests retrieval of DNS rules From 85d437582577f73b215b9f63e275668921ff1744 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 23:36:29 -0400 Subject: [PATCH 69/78] Added unit tests for #get_rule with an invalid id. Also overrode Kernel#puts to suppress output from RubyDNS. --- test/unit/extensions/tc_dns.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 952a132d3..f1bdc2970 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -106,14 +106,17 @@ class TC_Dns < Test::Unit::TestCase assert_not_nil(id =~ /^\h{7}$/) end - # Tests retrieval of DNS rules - def test_6_get_rule + # Tests retrieval of valid DNS rules + def test_8_get_rule_good id = @@dns.add_rule('be.ef', IN::A) do |transaction| transaction.respond!('1.1.1.1') end rule = @@dns.get_rule(id) + assert_equal(Hash, rule.class) + assert(rule.length > 0) + assert(rule.has_key?(:id)) assert(rule.has_key?(:pattern)) assert(rule.has_key?(:type)) @@ -125,9 +128,24 @@ class TC_Dns < Test::Unit::TestCase response = rule[:response] - assert_equal(Array, response.class) + assert(response.class == Array) assert(response.length > 0) assert_equal('1.1.1.1', response[0]) end + # Tests retrieval of invalid DNS rules + def test_9_get_rule_bad + rule = @@dns.get_rule(42) + + assert_equal(Hash, rule.class) + assert(rule.length == 0) + end + +end + +# Suppresses unnecessary output from RubyDNS +module Kernel + + def puts(*args); end + end From 94da775ba6e664ea3750cc20866c020218b6b226 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 15 Jul 2013 23:58:37 -0400 Subject: [PATCH 70/78] Added unit tests for #remove_rule (good and bad behavior). --- test/unit/extensions/tc_dns.rb | 35 +++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index f1bdc2970..761b2184b 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -32,19 +32,19 @@ class TC_Dns < Test::Unit::TestCase end # Connects to in-memory database (does not test anything) - def test_1_database + def test_01_database DataMapper.setup(:default, 'sqlite3::memory:') DataMapper.auto_migrate! end # Checks for required settings in config file - def test_2_config + def test_02_config assert(@@dns_config.has_key?('address')) assert(@@dns_config.has_key?('port')) end # Verifies public interface - def test_3_interface + def test_03_interface @@dns = BeEF::Extension::Dns::Server.instance assert_respond_to(@@dns, :run_server) @@ -55,7 +55,7 @@ class TC_Dns < Test::Unit::TestCase end # Starts DNS server (does not test anything) - def test_4_run_server + def test_04_run_server address = @@dns_config['address'] port = @@dns_config['port'] @@ -64,7 +64,7 @@ class TC_Dns < Test::Unit::TestCase end # Tests procedure for properly adding new DNS rules - def test_5_add_rule_good + def test_05_add_rule_good id = nil assert_nothing_raised do @@ -77,7 +77,7 @@ class TC_Dns < Test::Unit::TestCase end # Tests that adding existing rules returns current id - def test_6_add_rule_bad + def test_06_add_rule_bad id = nil same_id = nil @@ -97,7 +97,7 @@ class TC_Dns < Test::Unit::TestCase end # Verifies the proper format for rule identifiers - def test_7_id_format + def test_07_id_format id = @@dns.add_rule('dead.beef', IN::A) do |transaction| transaction.respond!('2.2.2.2') end @@ -107,7 +107,7 @@ class TC_Dns < Test::Unit::TestCase end # Tests retrieval of valid DNS rules - def test_8_get_rule_good + def test_08_get_rule_good id = @@dns.add_rule('be.ef', IN::A) do |transaction| transaction.respond!('1.1.1.1') end @@ -134,13 +134,30 @@ class TC_Dns < Test::Unit::TestCase end # Tests retrieval of invalid DNS rules - def test_9_get_rule_bad + def test_09_get_rule_bad rule = @@dns.get_rule(42) assert_equal(Hash, rule.class) assert(rule.length == 0) end + # Tests the removal of existing DNS rules + def test_10_remove_rule_good + id = @@dns.add_rule('hack.the.gibson', IN::A) do |transaction| + transaction.respond!('1.9.9.5') + end + + removed = @@dns.remove_rule(id) + + assert(removed) + end + + # Tests the removal of unknown DNS rules + def test_11_remove_rule_bad + removed = @@dns.remove_rule(42) + assert(!removed) + end + end # Suppresses unnecessary output from RubyDNS From 9cfb98963d1709cc85fdfa3e0a31ed93ab90f660 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 16 Jul 2013 23:48:46 -0400 Subject: [PATCH 71/78] Added unit tests for #get_ruleset. --- test/unit/extensions/tc_dns.rb | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 761b2184b..092518dd2 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -128,7 +128,7 @@ class TC_Dns < Test::Unit::TestCase response = rule[:response] - assert(response.class == Array) + assert_equal(Array, response.class) assert(response.length > 0) assert_equal('1.1.1.1', response[0]) end @@ -138,7 +138,7 @@ class TC_Dns < Test::Unit::TestCase rule = @@dns.get_rule(42) assert_equal(Hash, rule.class) - assert(rule.length == 0) + assert_equal(0, rule.length) end # Tests the removal of existing DNS rules @@ -158,6 +158,29 @@ class TC_Dns < Test::Unit::TestCase assert(!removed) end + # 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] } + + assert_equal(Array, ruleset.class) + assert_equal(4, ruleset.length) + + check_rule(ruleset[0], {:pattern => 'be.ef', :type => 'A', :response => '1.1.1.1'}) + check_rule(ruleset[1], {:pattern => 'dead.beef', :type => 'A', :response => '2.2.2.2'}) + check_rule(ruleset[2], {:pattern => 'foo.bar', :type => 'A', :response => '1.2.3.4'}) + check_rule(ruleset[3], {:pattern => 'j.random.hacker', :type => 'A', :response => '4.2.4.2'}) + end + + private + + # Compares each key in hash 'rule' with the respective key in hash 'expected' + def check_rule(rule, expected = {}) + assert_equal(expected[:pattern], rule[:pattern]) + assert_equal(expected[:type], rule[:type]) + assert_equal(expected[:response], rule[:response][0]) + end + end # Suppresses unnecessary output from RubyDNS From 95d0ddbe8725c325bcb81d5d2255bc7e3c1ba454 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 17 Jul 2013 18:16:46 -0400 Subject: [PATCH 72/78] Added new method #remove_ruleset that clears the entire DNS ruleset. Included unit tests as well. --- extensions/dns/dns.rb | 19 +++++++++---- extensions/dns/ruby/rubydns.rb | 52 +++++++++++++++++++--------------- test/unit/extensions/tc_dns.rb | 10 +++++++ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 2318b16e5..9a97bba98 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -83,6 +83,15 @@ module Dns @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: @@ -97,13 +106,13 @@ module Dns @lock.synchronize { @server.get_ruleset } end - # Retrieves a specific rule given its id + # Clears the entire DNS ruleset. # - # @param id [Integer] unique identifier for rule + # Requests made after doing so will be passed through to the root nameservers. # - # @return [Hash] hash representation of rule - def get_rule(id) - @lock.synchronize { @server.get_rule(id) } + # @return [Boolean] true on success, false on failure + def remove_ruleset + @lock.synchronize { @server.remove_ruleset } end private diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 4e2c795bd..5f54f8fcb 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -55,6 +55,17 @@ module RubyDNS 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] + block = eval rule.block + + @rules << Rule.new(id, pattern, block) + end + end + # Now includes BeEF database support and checks for already present rules def match(*pattern, block) id = '' @@ -117,15 +128,22 @@ module RubyDNS rule != nil ? rule.destroy : false 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] - block = eval rule.block + # New method that returns a hash representing the given rule + def get_rule(id) + result = {} - @rules << Rule.new(id, pattern, block) + 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 @@ -146,22 +164,10 @@ module RubyDNS result 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 + # New method that removes the entire DNS ruleset + def remove_ruleset + @rules = [] + BeEF::Core::Models::Dns::Rule.destroy end private diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 092518dd2..638ee6fa1 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -155,6 +155,7 @@ class TC_Dns < Test::Unit::TestCase # Tests the removal of unknown DNS rules def test_11_remove_rule_bad removed = @@dns.remove_rule(42) + assert(!removed) end @@ -172,6 +173,15 @@ class TC_Dns < Test::Unit::TestCase check_rule(ruleset[3], {:pattern => 'j.random.hacker', :type => 'A', :response => '4.2.4.2'}) end + # Tests the removal of the entire DNS ruleset + def test_13_remove_ruleset + removed = @@dns.remove_ruleset + ruleset = @@dns.get_ruleset + + assert(removed) + assert_equal(0, ruleset.length) + end + private # Compares each key in hash 'rule' with the respective key in hash 'expected' From 141a12a92fbcafb8dd8129aca51f1ecba3dbf724 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 17 Jul 2013 18:19:56 -0400 Subject: [PATCH 73/78] Included #remove_ruleset in public interface tests. --- test/unit/extensions/tc_dns.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 638ee6fa1..5ad8a8d50 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -50,8 +50,9 @@ class TC_Dns < Test::Unit::TestCase assert_respond_to(@@dns, :run_server) assert_respond_to(@@dns, :add_rule) assert_respond_to(@@dns, :remove_rule) - assert_respond_to(@@dns, :get_ruleset) assert_respond_to(@@dns, :get_rule) + assert_respond_to(@@dns, :get_ruleset) + assert_respond_to(@@dns, :remove_ruleset) end # Starts DNS server (does not test anything) From 8d961c19384aee16dc088b734b9144adb40fa209 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 19 Jul 2013 22:15:25 -0400 Subject: [PATCH 74/78] Added support for rules that fail to resolve (e.g. NXDOMAIN). Included unit tests. --- extensions/dns/ruby/rubydns.rb | 5 ++- test/unit/extensions/tc_dns.rb | 77 ++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 5f54f8fcb..37ef067e5 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -189,7 +189,8 @@ module RubyDNS # New method that parses response callback and returns RDATA as an array def parse_response(block) # Extract response arguments into an array - args = /(?<=respond!\().*(?=\))/.match(block).to_s.split(/,\s*/) + methods = '(respond|failure)' + args = /(?<=\.#{methods}!\().*(?=\))/.match(block).to_s.split(/,\s*/) result = [] @@ -199,6 +200,8 @@ module RubyDNS 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) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 5ad8a8d50..a1792e2aa 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -183,6 +183,65 @@ class TC_Dns < Test::Unit::TestCase assert_equal(0, ruleset.length) end + # Tests each supported type of query failure + def test_14_failure_types + begin + id = @@dns.add_rule('noerror.beef.com', IN::A) do |transaction| + transaction.failure!(:NoError) + end + + check_failure_status(id, :NoError) + end + + begin + id = @@dns.add_rule('formerr.beef.com', IN::A) do |transaction| + transaction.failure!(:FormErr) + end + + check_failure_status(id, :FormErr) + end + + begin + id = @@dns.add_rule('servfail.beef.com', IN::A) do |transaction| + transaction.failure!(:ServFail) + end + + check_failure_status(id, :ServFail) + end + + begin + id = @@dns.add_rule('nxdomain.beef.com', IN::A) do |transaction| + transaction.failure!(:NXDomain) + end + + check_failure_status(id, :NXDomain) + end + + begin + id = @@dns.add_rule('notimp.beef.com', IN::A) do |transaction| + transaction.failure!(:NotImp) + end + + check_failure_status(id, :NotImp) + end + + begin + id = @@dns.add_rule('refused.beef.com', IN::A) do |transaction| + transaction.failure!(:Refused) + end + + check_failure_status(id, :Refused) + end + + begin + id = @@dns.add_rule('notauth.beef.com', IN::A) do |transaction| + transaction.failure!(:NotAuth) + end + + check_failure_status(id, :NotAuth) + end + end + private # Compares each key in hash 'rule' with the respective key in hash 'expected' @@ -192,6 +251,24 @@ class TC_Dns < Test::Unit::TestCase assert_equal(expected[:response], rule[:response][0]) end + # Compares output of dig command against regex + def check_dns_response(regex, type, pattern) + address = @@dns_config['address'] + port = @@dns_config['port'] + + dig_output = `dig @#{address} -p #{port} -t #{type} #{pattern}` + assert_match(regex, dig_output) + end + + # 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) + status = type.to_s.force_encoding('UTF-8').upcase + + assert_equal(status, rule[:response][0]) + check_dns_response(/status: #{status}/, rule[:type], rule[:pattern]) + end + end # Suppresses unnecessary output from RubyDNS From 6a62cf9eaa54239fc397c2983934c4e5fd2f7188 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Fri, 19 Jul 2013 22:33:40 -0400 Subject: [PATCH 75/78] Added public attributes 'address' and 'port' to Dns::Server. This removes the need to search config.yaml for the address:port. Also included unit tests. --- extensions/dns/dns.rb | 9 +++++++-- test/unit/extensions/tc_dns.rb | 19 +++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 9a97bba98..362ec519e 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -17,6 +17,8 @@ module Dns include Singleton + attr_reader :address, :port + # @!method self.instance # Returns the singleton instance. Use this in place of {#initialize}. @@ -32,6 +34,9 @@ module Dns # @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 @@ -40,9 +45,9 @@ module Dns sleep(1) if EventMachine.reactor_running? - EventMachine.next_tick { run_server_block(address, port) } + EventMachine.next_tick { run_server_block(@address, @port) } else - run_server_block(address, port) + run_server_block(@address, @port) end end end diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index a1792e2aa..9d6f358a7 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -55,13 +55,16 @@ class TC_Dns < Test::Unit::TestCase assert_respond_to(@@dns, :remove_ruleset) end - # Starts DNS server (does not test anything) + # 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 # Tests procedure for properly adding new DNS rules @@ -251,22 +254,14 @@ class TC_Dns < Test::Unit::TestCase assert_equal(expected[:response], rule[:response][0]) end - # Compares output of dig command against regex - def check_dns_response(regex, type, pattern) - address = @@dns_config['address'] - port = @@dns_config['port'] - - dig_output = `dig @#{address} -p #{port} -t #{type} #{pattern}` - assert_match(regex, dig_output) - end - # 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) status = type.to_s.force_encoding('UTF-8').upcase - assert_equal(status, rule[:response][0]) - check_dns_response(/status: #{status}/, rule[:type], rule[:pattern]) + + dig_output = `dig @#{@@dns.address} -p #{@@dns.port} -t #{rule[:type]} #{rule[:pattern]}` + assert_match(/status: #{status}/, dig_output) end end From b2aed14234f97b107deb5bb5dcdd1e6770a51f83 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 22 Jul 2013 22:37:39 -0400 Subject: [PATCH 76/78] Added regex support to #add_rule (tests included). Due to strange behavior in Sourcify, the /.../ literal syntax cannot be used as a parameter; only %r{} or Regexp::new. There is a note for this in the documentation for #add_rule. --- extensions/dns/dns.rb | 6 +++- extensions/dns/ruby/rubydns.rb | 5 ++- test/unit/extensions/tc_dns.rb | 58 +++++++++++++++++++++++++++------- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/extensions/dns/dns.rb b/extensions/dns/dns.rb index 362ec519e..050f5f37e 100644 --- a/extensions/dns/dns.rb +++ b/extensions/dns/dns.rb @@ -66,10 +66,14 @@ module Dns # @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 [Integer] unique identifier for use with {#remove_rule} + # @return [String] unique 7-digit hex identifier for use with {#remove_rule} # # @see #remove_rule # @see http://rubydoc.info/gems/rubydns/RubyDNS/Transaction diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 37ef067e5..021cf1cea 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -62,6 +62,9 @@ module RubyDNS pattern = [rule.pattern, rule.type] block = eval rule.block + regex = pattern[0] + pattern[0] = Regexp.new(regex) if regex =~ /^\(\?-mix:/ + @rules << Rule.new(id, pattern, block) end end @@ -101,7 +104,7 @@ module RubyDNS BeEF::Core::Models::Dns::Rule.create( :id => id, - :pattern => pattern[0], + :pattern => pattern[0].to_s, :type => pattern[1], :block => block_src ) diff --git a/test/unit/extensions/tc_dns.rb b/test/unit/extensions/tc_dns.rb index 9d6f358a7..f6fe67687 100644 --- a/test/unit/extensions/tc_dns.rb +++ b/test/unit/extensions/tc_dns.rb @@ -69,22 +69,42 @@ class TC_Dns < Test::Unit::TestCase # Tests procedure for properly adding new DNS rules def test_05_add_rule_good - id = nil + id1 = nil + id2 = nil assert_nothing_raised do - id = @@dns.add_rule('foo.bar', IN::A) do |transaction| + id1 = @@dns.add_rule('foo.bar', IN::A) do |transaction| transaction.respond!('1.2.3.4') end end - assert_not_nil(id) + assert_not_nil(id1) + + assert_nothing_raised do + id2 = @@dns.add_rule(%r{i\.(love|hate)\.beef\.com?}, IN::A) do |transaction| + transaction.respond!('9.9.9.9') + end + end + + assert_not_nil(id2) + + domain1 = 'i.hate.beef.com' + domain2 = 'i.love.beef.com' + domain3 = 'i.love.beef.co' + domain4 = 'i.love.beef.co' + + [domain1, domain2, domain3, domain4].each do |domain| + regex = /^#{domain}\.\t+\d+\t+IN\t+A\t+9\.9\.9\.9$/ + check_dns_response(regex, 'A', domain) + end end - # Tests that adding existing rules returns current id + # Tests addition of new rules with invalid parameters def test_06_add_rule_bad id = nil same_id = nil + # Add the same rule twice assert_nothing_raised do id = @@dns.add_rule('j.random.hacker', IN::A) do |transaction| transaction.respond!('4.2.4.2') @@ -98,6 +118,13 @@ class TC_Dns < Test::Unit::TestCase end assert_equal(id, same_id) + + # Use /.../ literal syntax to throw Sourcify exception + assert_raise do + id = @@dns.add_rule(/.*/, IN::A) do |transaction| + transaction.respond!('5.1.5.0') + end + end end # Verifies the proper format for rule identifiers @@ -169,12 +196,16 @@ class TC_Dns < Test::Unit::TestCase ruleset.sort! {|a, b| a[:pattern] <=> b[:pattern] } assert_equal(Array, ruleset.class) - assert_equal(4, ruleset.length) + assert_equal(5, ruleset.length) - check_rule(ruleset[0], {:pattern => 'be.ef', :type => 'A', :response => '1.1.1.1'}) - check_rule(ruleset[1], {:pattern => 'dead.beef', :type => 'A', :response => '2.2.2.2'}) - check_rule(ruleset[2], {:pattern => 'foo.bar', :type => 'A', :response => '1.2.3.4'}) - check_rule(ruleset[3], {:pattern => 'j.random.hacker', :type => 'A', :response => '4.2.4.2'}) + check_rule(ruleset[0], {:pattern=>'(?-mix:i\\.(love|hate)\\.beef\\.com?)', + :type => 'A', + :response => '9.9.9.9'}) + + check_rule(ruleset[1], {:pattern => 'be.ef', :type => 'A', :response => '1.1.1.1'}) + check_rule(ruleset[2], {:pattern => 'dead.beef', :type => 'A', :response => '2.2.2.2'}) + check_rule(ruleset[3], {:pattern => 'foo.bar', :type => 'A', :response => '1.2.3.4'}) + check_rule(ruleset[4], {:pattern => 'j.random.hacker', :type => 'A', :response => '4.2.4.2'}) end # Tests the removal of the entire DNS ruleset @@ -260,8 +291,13 @@ class TC_Dns < Test::Unit::TestCase status = type.to_s.force_encoding('UTF-8').upcase assert_equal(status, rule[:response][0]) - dig_output = `dig @#{@@dns.address} -p #{@@dns.port} -t #{rule[:type]} #{rule[:pattern]}` - assert_match(/status: #{status}/, dig_output) + check_dns_response(/status: #{status}/, rule[:type], rule[:pattern]) + end + + # Compares output of dig command against regex + def check_dns_response(regex, type, pattern) + dig_output = `dig @#{@@dns.address} -p #{@@dns.port} -t #{type} #{pattern}` + assert_match(regex, dig_output) end end From 9d4ea6c224fe15eba46ff9e53220968fc91c8a20 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Mon, 22 Jul 2013 22:42:27 -0400 Subject: [PATCH 77/78] Fixed issue mentioned in FIXME comment in RubyDNS::Server#match. Changed 'block.class.name' to just 'block' in case/when clause. --- extensions/dns/ruby/rubydns.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/dns/ruby/rubydns.rb b/extensions/dns/ruby/rubydns.rb index 021cf1cea..ded90a681 100644 --- a/extensions/dns/ruby/rubydns.rb +++ b/extensions/dns/ruby/rubydns.rb @@ -94,8 +94,7 @@ module RubyDNS id = generate_id - # FIXME Use block - case block.class.name + case block when String @rules << Rule.new(id, pattern, eval(block_src)) when Proc From a75a95b66303a462bee09624f2cbf14f39a4fc67 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Tue, 4 Feb 2014 16:18:12 -0500 Subject: [PATCH 78/78] Implemented DNS spoofer in social engineering extension. The /api/seng/clone_page endpoint now accepts a boolean "dns_spoof" key in the JSON request. This adds a DNS record pointing the cloned webpage to the BeEF server. Integration tests included. --- Rakefile | 8 -- .../rest/socialengineering.rb | 19 ++-- .../web_cloner/web_cloner.rb | 16 +++- test/common/test_constants.rb | 1 + .../integration/tc_social_engineering_rest.rb | 91 +++++++++++++++++++ test/integration/ts_integration.rb | 2 + 6 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 test/integration/tc_social_engineering_rest.rb diff --git a/Rakefile b/Rakefile index fe92286e7..e40188318 100644 --- a/Rakefile +++ b/Rakefile @@ -32,14 +32,6 @@ task :integration => ["install"] do Rake::Task['beef_stop'].invoke end -# TODO: Remove task before comitting -desc "Run DNS integration tests" -task :dns_rest do - Rake::Task['beef_start'].invoke - sh "cd test/integration; ruby -W0 ts_dns_rest.rb" - Rake::Task['beef_stop'].invoke -end - desc "Run integration unit tests" task :unit => ["install"] do sh "cd test/unit;ruby -W0 ts_unit.rb" diff --git a/extensions/social_engineering/rest/socialengineering.rb b/extensions/social_engineering/rest/socialengineering.rb index b03447145..c9a4deaeb 100644 --- a/extensions/social_engineering/rest/socialengineering.rb +++ b/extensions/social_engineering/rest/socialengineering.rb @@ -20,10 +20,15 @@ module BeEF 'Expires' => '0' end - #Example: curl -H "Content-Type: application/json; charset=UTF-8" - #-d '{"url":"https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue= - #https://mail.google.com/mail/&ss=1&scc=1<mpl=default<mplcache=2", "mount":"/url"}' - #-X POST http://127.0.0.1:3000/api/seng/clone_page?token=851a937305f8773ee82f5259e792288cdcb01cd7 + # Example: curl -H "Content-Type: application/json; charset=UTF-8" -d json_body + # -X POST http://127.0.0.1:3000/api/seng/clone_page?token=851a937305f8773ee82f5259e792288cdcb01cd7 + # + # Example json_body: + # { + # "url": "https://accounts.google.com/ServiceLogin?service=mail&continue=https://mail.google.com/mail/" + # "mount": "/gmail", + # "dns_spoof": true + # } post '/clone_page' do request.body.rewind begin @@ -31,6 +36,7 @@ module BeEF uri = body["url"] mount = body["mount"] use_existing = body["use_existing"] + dns_spoof = body["dns_spoof"] if uri != nil && mount != nil if (uri =~ URI::regexp).nil? #invalid URI @@ -44,7 +50,8 @@ module BeEF end web_cloner = BeEF::Extension::SocialEngineering::WebCloner.instance - success = web_cloner.clone_page(uri,mount,use_existing) + success = web_cloner.clone_page(uri, mount, use_existing, dns_spoof) + if success result = { "success" => true, @@ -118,4 +125,4 @@ module BeEF end end end -end \ No newline at end of file +end diff --git a/extensions/social_engineering/web_cloner/web_cloner.rb b/extensions/social_engineering/web_cloner/web_cloner.rb index 9b98e3537..5cd9bca5c 100644 --- a/extensions/social_engineering/web_cloner/web_cloner.rb +++ b/extensions/social_engineering/web_cloner/web_cloner.rb @@ -7,9 +7,9 @@ module BeEF module Extension module SocialEngineering class WebCloner + require 'socket' include Singleton - def initialize @http_server = BeEF::Core::Server.instance @config = BeEF::Core::Configuration.instance @@ -17,7 +17,7 @@ module BeEF @beef_hook = "http://#{@config.get('beef.http.host')}:#{@config.get('beef.http.port')}#{@config.get('beef.http.hook_file')}" end - def clone_page(url, mount, use_existing) + def clone_page(url, mount, use_existing, dns_spoof) print_info "Cloning page at URL #{url}" uri = URI(url) output = uri.host @@ -113,6 +113,18 @@ module BeEF @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?)} + domain = url.gsub(%r{^http://}, '') + + id = dns.add_rule(domain, Resolv::DNS::Resource::IN::A) do |transaction| + transaction.respond!(ip.ip_address) + end + end + 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/common/test_constants.rb b/test/common/test_constants.rb index b28f315f6..58cea47e7 100644 --- a/test/common/test_constants.rb +++ b/test/common/test_constants.rb @@ -20,4 +20,5 @@ RESTAPI_HOOKS = "http://" + ATTACK_DOMAIN + ":3000/api/hooks" RESTAPI_LOGS = "http://" + ATTACK_DOMAIN + ":3000/api/logs" RESTAPI_MODULES = "http://" + ATTACK_DOMAIN + ":3000/api/modules" RESTAPI_DNS = "http://" + ATTACK_DOMAIN + ":3000/api/dns" +RESTAPI_SENG = "http://" + ATTACK_DOMAIN + ":3000/api/seng" RESTAPI_ADMIN = "http://" + ATTACK_DOMAIN + ":3000/api/admin" diff --git a/test/integration/tc_social_engineering_rest.rb b/test/integration/tc_social_engineering_rest.rb new file mode 100644 index 000000000..9895bb0f3 --- /dev/null +++ b/test/integration/tc_social_engineering_rest.rb @@ -0,0 +1,91 @@ +# +# 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 'test/unit' +require 'rest_client' +require 'json' +require '../common/test_constants' + +# @todo RESTful API for the social engineering extension lacks some serious test coverage. +class TC_SocialEngineeringRest < Test::Unit::TestCase + + class << self + + # Login to API before performing any tests + def startup + json = {:username => BEEF_USER, :password => BEEF_PASSWD}.to_json + @@headers = {:content_type => :json, :accept => :json} + + response = RestClient.post("#{RESTAPI_ADMIN}/login", + json, + @@headers) + + result = JSON.parse(response.body) + @@token = result['token'] + + $root_dir = '../../' + $:.unshift($root_dir) + + require 'core/loader' + + BeEF::Core::Configuration.new(File.join($root_dir, 'config.yaml')) + BeEF::Core::Configuration.instance.load_extensions_config + + @@config = BeEF::Core::Configuration.instance + end + + def shutdown + $root_dir = nil + end + + end + + # Tests DNS spoofing of cloned webpages + def test_1_dns_spoof + url = 'http://beefproject.com' + mount = '/beefproject' + dns_spoof = true + + json = {:url => url, :mount => mount, :dns_spoof => dns_spoof}.to_json + + response = RestClient.post("#{RESTAPI_SENG}/clone_page?token=#{@@token}", + json, + @@headers) + + check_response(response) + + ip = Socket.ip_address_list.detect {|i| !(i.ipv4_loopback? || i.ipv6_loopback?)} + domain = url.gsub(%r{^http://}, '') + + regex = %r{ + ^#{domain}\.\t+ + \d+\t+ + IN\t+ + A\t+ + #{ip.ip_address}$ + }x + + # 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') + + dig_output = `dig @#{dns_address} -p #{dns_port} -t A #{domain}` + assert_match(regex, dig_output) + end + + private + + # Assertions for verifying a response from the RESTful API + def check_response(response) + assert_not_nil(response.body) + assert_equal(200, response.code) + + result = JSON.parse(response.body) + + assert(result['success']) + assert(result['mount']) + end + +end diff --git a/test/integration/ts_integration.rb b/test/integration/ts_integration.rb index d25ef06c7..3598ffb36 100644 --- a/test/integration/ts_integration.rb +++ b/test/integration/ts_integration.rb @@ -17,6 +17,7 @@ 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_social_engineering_rest' # Basic tests for social engineering RESTful API interface class TS_BeefIntegrationTests def self.suite @@ -27,6 +28,7 @@ class TS_BeefIntegrationTests suite << TC_DebugModules.suite suite << TC_Jools.suite suite << TC_DnsRest.suite + suite << TC_SocialEngineeringRest.suite return suite end