From e8b99343a687853ce6e9400f622fcafa44011118 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 19 Mar 2024 09:13:21 -0400 Subject: [PATCH 1/4] Add static entries examples and fix black hole --- .../How-to-Configure-DNS.md | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/docs/metasploit-framework.wiki/How-to-Configure-DNS.md b/docs/metasploit-framework.wiki/How-to-Configure-DNS.md index 43641a5146a5..86d2147228f4 100644 --- a/docs/metasploit-framework.wiki/How-to-Configure-DNS.md +++ b/docs/metasploit-framework.wiki/How-to-Configure-DNS.md @@ -64,9 +64,9 @@ handling the request. Different resolver types can be specified to handle querie in numeric order starting at position 1. Rules can be added to or removed from specific positions in a similar manner to how iptables rules can be added to and removed from a specific chain. -### The Blackhole Resolver -The blackhole resolver can be used to prevent queries from being resolved. It handles all query types and will prevent -resolvers defined after it from being used. The blackhole resolver is specified by using the `blackhole` keyword. +### The Black Hole Resolver +The black hole resolver can be used to prevent queries from being resolved. It handles all query types and will prevent +resolvers defined after it from being used. The black hole resolver is specified by using the `black-hole` keyword. ### The Upstream Resolver An upstream resolver can be used by specifying either an IPv4 or IPv6 address. When Metasploit uses this resolver, the @@ -106,6 +106,12 @@ Append a rule to the end that will handle all queries for `*.lab.lan` using an u dns add --rule *.lab.lan --session 1 192.0.2.1 ``` +Append a rule to drop all queries for `*.noresolve.lan` using the black hole resolver. + +``` +dns add --rule *.noresolve.lan black-hole +``` + ## Static DNS Entries Static entries used by the static resolver are configured through the `add-static` and `remove-static` subcommands. The currently configured entries can be viewed in the `dns print` output and all entries can be flushed with the @@ -114,6 +120,28 @@ is specified. In order for the static entry to be used, at least one rule must m configured to use the static resolver. A single hostname can be associated with multiple IP addresses and the same IP address can be associated with multiple hostnames. +### Example Static Entries + +Define static entries for `localhost` and common variations. + +``` +dns add-static localhost 127.0.0.1 ::1 +dns add-static localhost4 127.0.0.1 +dns add-static localhost6 ::1 +``` + +Remove all static entries for `localhost`. + +``` +dns remove-static localhost +``` + +Remove all static entries. + +``` +dns flush-static +``` + ## The DNS Cache DNS query replies are cached internally by Metasploit based on their TTL. This intends to minimize the amount of network traffic required to perform the necessary lookups. The number of query replies that are currently cached is available in From 5b1d0100d2513acc930dc27942b900fe96338602 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 19 Mar 2024 09:36:51 -0400 Subject: [PATCH 2/4] Add spell checking for resolvers --- lib/msf/ui/console/command_dispatcher/dns.rb | 9 +++++++-- lib/rex/proto/dns/upstream_rule.rb | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/dns.rb b/lib/msf/ui/console/command_dispatcher/dns.rb index 4c24e38d192b..17c5950a0c40 100755 --- a/lib/msf/ui/console/command_dispatcher/dns.rb +++ b/lib/msf/ui/console/command_dispatcher/dns.rb @@ -283,7 +283,12 @@ def add_dns(*args) resolvers.each do |resolver| unless Rex::Proto::DNS::UpstreamRule.valid_resolver?(resolver) - raise ::ArgumentError.new("Invalid DNS resolver: #{resolver}") + message = "Invalid DNS resolver: #{resolver}." + if (suggestions = Rex::Proto::DNS::UpstreamRule.spell_check_resolver(resolver)).present? + message << " Did you mean #{suggestions.first}?" + end + + raise ::ArgumentError.new(message) end end @@ -302,7 +307,7 @@ def add_dns(*args) end rules.each_with_index do |rule, offset| - print_warning("DNS rule #{rule} does not contain wildcards, so will not match subdomains") unless rule.include?('*') + print_warning("DNS rule #{rule} does not contain wildcards, it will not match subdomains") unless rule.include?('*') driver.framework.dns_resolver.add_upstream_rule( resolvers, comm: comm_obj, diff --git a/lib/rex/proto/dns/upstream_rule.rb b/lib/rex/proto/dns/upstream_rule.rb index 349ae3f6e76c..dc6b13113403 100644 --- a/lib/rex/proto/dns/upstream_rule.rb +++ b/lib/rex/proto/dns/upstream_rule.rb @@ -61,6 +61,23 @@ def self.valid_resolver?(resolver) ].include?(resolver) end + # Perform a spell check on resolver to suggest corrections. + # + # @param [String] resolver The resolver string to check. + # @rtype [Nil, Array] The suggestions if resolver is invalid. + def self.spell_check_resolver(resolver) + return nil if Rex::Socket.is_ip_addr?(resolver) + + suggestions = DidYouMean::SpellChecker.new(dictionary: [ + UpstreamResolver::Type::BLACK_HOLE, + UpstreamResolver::Type::STATIC, + UpstreamResolver::Type::SYSTEM + ]).correct(resolver).map(&:to_s) + return nil if suggestions.empty? + + suggestions + end + # Check whether or not the defined wildcard is a valid pattern. # # @param [String] wildcard The wildcard text to check. From 0cf473731734fe08a906597f8f9265e7da5c4f4b Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 19 Mar 2024 09:45:30 -0400 Subject: [PATCH 3/4] Add specs for resolver spell checking --- spec/lib/rex/proto/dns/upstream_rule_spec.rb | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/lib/rex/proto/dns/upstream_rule_spec.rb b/spec/lib/rex/proto/dns/upstream_rule_spec.rb index 48aec5bb4e75..3c7c585f645d 100644 --- a/spec/lib/rex/proto/dns/upstream_rule_spec.rb +++ b/spec/lib/rex/proto/dns/upstream_rule_spec.rb @@ -3,6 +3,28 @@ require 'rex/text' RSpec.describe Rex::Proto::DNS::UpstreamRule do + describe '.spell_check_resolver' do + it 'returns nil for IPv4 addresses' do + address = Rex::Socket.addr_ntoa(Random.new.bytes(4)) + expect(described_class.spell_check_resolver(address)).to be_nil + end + + it 'returns nil for IPv6 addresses' do + address = Rex::Socket.addr_ntoa(Random.new.bytes(16)) + expect(described_class.spell_check_resolver(address)).to be_nil + end + + it 'returns nil for "black-hole"' do + expect(described_class.spell_check_resolver('black-hole')).to be_nil + end + + it 'returns a populated array for "blackhole"' do + suggestions = described_class.spell_check_resolver('blackhole') + expect(suggestions).to be_a Array + expect(suggestions.first).to eq 'black-hole' + end + end + describe '.valid_resolver?' do it 'returns true for "black-hole"' do expect(described_class.valid_resolver?('black-hole')).to be_truthy From b3b6f7959442ff720b85cd184e5e0d2a5c89df80 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 19 Mar 2024 10:14:13 -0400 Subject: [PATCH 4/4] Update the presentation of static entries Keep the first line blank for consistency with rules and sort hostnames and addresses. --- lib/msf/ui/console/command_dispatcher/dns.rb | 15 ++++++++++----- lib/rex/proto/dns/static_hostnames.rb | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/dns.rb b/lib/msf/ui/console/command_dispatcher/dns.rb index 17c5950a0c40..05954952a8ed 100755 --- a/lib/msf/ui/console/command_dispatcher/dns.rb +++ b/lib/msf/ui/console/command_dispatcher/dns.rb @@ -644,11 +644,16 @@ def print_dns 'SortIndex' => -1, 'WordWrap' => false ) - resolver.static_hostnames.each do |hostname, addresses| - ipv4_addresses = addresses.fetch(Dnsruby::Types::A, []) - ipv6_addresses = addresses.fetch(Dnsruby::Types::AAAA, []) - 0.upto([ipv4_addresses.length, ipv6_addresses.length].max - 1) do |idx| - tbl << [idx == 0 ? hostname : TABLE_INDENT, ipv4_addresses[idx], ipv6_addresses[idx]] + resolver.static_hostnames.sort_by { |hostname, _| hostname }.each do |hostname, addresses| + ipv4_addresses = addresses.fetch(Dnsruby::Types::A, []).sort_by(&:to_i) + ipv6_addresses = addresses.fetch(Dnsruby::Types::AAAA, []).sort_by(&:to_i) + if (ipv4_addresses.length <= 1 && ipv6_addresses.length <= 1) && ((ipv4_addresses + ipv6_addresses).length > 0) + tbl << [hostname, ipv4_addresses.first, ipv6_addresses.first] + else + tbl << [hostname, '', ''] + 0.upto([ipv4_addresses.length, ipv6_addresses.length].max - 1) do |idx| + tbl << [TABLE_INDENT, ipv4_addresses[idx], ipv6_addresses[idx]] + end end end print_line(tbl.to_s) diff --git a/lib/rex/proto/dns/static_hostnames.rb b/lib/rex/proto/dns/static_hostnames.rb index 4b43771b616b..ce2f3b928b79 100644 --- a/lib/rex/proto/dns/static_hostnames.rb +++ b/lib/rex/proto/dns/static_hostnames.rb @@ -13,7 +13,7 @@ module DNS class StaticHostnames extend Forwardable - def_delegators :@hostnames, :each, :each_with_index, :length, :empty? + def_delegators :@hostnames, :each, :each_with_index, :length, :empty?, :sort_by # @param [Hash] hostnames The hostnames to IP address mappings to initialize with. def initialize(hostnames: nil)