Path: blob/master/lib/rex/proto/dns/custom_nameserver_provider.rb
19670 views
require 'rex/proto/dns/upstream_resolver'12module Rex3module Proto4module DNS56##7# Provides a DNS resolver the ability to use different nameservers8# for different requests, based on the domain being queried.9##10module CustomNameserverProvider11CONFIG_KEY_BASE = 'framework/dns'12CONFIG_VERSION = Rex::Version.new('1.0')1314#15# A Comm implementation that always reports as dead, so should never16# be used. This is used to prevent DNS leaks of saved DNS rules that17# were attached to a specific channel.18##19class CommSink20include Msf::Session::Comm21def alive?22false23end2425def supports_udp?26# It won't be used anyway, so let's just say we support it27true28end2930def sid31'previous MSF session'32end33end3435def init36@upstream_rules = []3738resolvers = [UpstreamResolver.create_static]39if @config[:nameservers].empty?40# if no nameservers are specified, fallback to the system41resolvers << UpstreamResolver.create_system42else43# migrate the originally configured name servers44resolvers += @config[:nameservers].map(&:to_s)45@config[:nameservers].clear46end4748add_upstream_rule(resolvers)4950nil51end5253# Reinitialize the configuration to its original state.54def reinit55parse_config_file56parse_environment_variables5758self.static_hostnames.flush59self.static_hostnames.parse_hosts_file6061init6263cache.flush if respond_to?(:cache)6465nil66end6768# Check whether or not there is configuration data in Metasploit's configuration file which is persisted on disk.69def has_config?70config = Msf::Config.load71version = config.fetch(CONFIG_KEY_BASE, {}).fetch('configuration_version', nil)72if version.nil?73@logger.info 'DNS configuration can not be loaded because the version is missing'74return false75end7677their_version = Rex::Version.new(version)78if their_version > CONFIG_VERSION # if the config is newer, it's incompatible (we only guarantee backwards compat)79@logger.info "DNS configuration version #{their_version} can not be loaded because it is too new"80return false81end8283my_minimum_version = Rex::Version.new(CONFIG_VERSION.canonical_segments.first.to_s)84if their_version < my_minimum_version # can not be older than our major version85@logger.info "DNS configuration version #{their_version} can not be loaded because it is too old"86return false87end8889true90end9192#93# Save the custom settings to the MSF config file94#95def save_config96new_config = {97'configuration_version' => CONFIG_VERSION.to_s98}99Msf::Config.save(CONFIG_KEY_BASE => new_config)100101save_config_upstream_rules102save_config_static_hostnames103end104105#106# Load the custom settings from the MSF config file107#108def load_config109unless has_config?110raise ResolverError.new('There is no compatible configuration data to load')111end112113load_config_entries114load_config_static_hostnames115end116117# Add a custom nameserver entry to the custom provider.118#119# @param [Array<String>] resolvers The list of upstream resolvers that would be used for this custom rule.120# @param [Msf::Session::Comm] comm The communication channel to be used for these DNS requests.121# @param [String] wildcard The wildcard rule to match a DNS request against.122# @param [Integer] index The index at which to insert the rule, defaults to -1 to append it at the end.123def add_upstream_rule(resolvers, comm: nil, wildcard: '*', index: -1)124resolvers = [resolvers] if resolvers.is_a?(String) # coerce into an array of strings125126@upstream_rules.insert(index, UpstreamRule.new(127wildcard: wildcard,128resolvers: resolvers,129comm: comm130))131end132133#134# Remove upstream rules with the given indices135# Ignore entries that are not found136# @param ids [Array<Integer>] The IDs to remove137# @return [Array<UpstreamRule>] The removed entries138def remove_ids(ids)139removed = []140ids.sort.reverse.each do |id|141upstream_rule = @upstream_rules.delete_at(id)142removed << upstream_rule if upstream_rule143end144145removed.reverse146end147148#149# Move upstream rules with the given indices into the location provided.150# If multiple IDs are provided, they will all be inserted into the provided location,151# in the order provided.152# Ignore entries that are not found153# @param ids [Array<Integer>] The IDs to move154# @param insertion_id [Integer] The ID to insert the entries at (in the order provided), or -1 to insert at the end155# @return [Array<UpstreamRule>] The moved entries156def reorder_ids(ids, new_id)157if new_id == -1158new_id = @upstream_rules.length159end160if new_id > @upstream_rules.length161raise ::ArgumentError.new("Insertion ID is past the end of the ruleset")162end163to_move = []164to_subtract = 0165# Get the entries before we delete (gets too complicated with indices changing otherwise)166ids.each do |id|167upstream_rule = @upstream_rules[id]168unless upstream_rule.nil?169to_move << upstream_rule170if new_id > id171to_subtract += 1 # Adjust for the fact that are about to delete one, so the indices would be off-by-one after that index is deleted172end173end174end175176new_id -= to_subtract177178ids.sort.reverse.each do |id|179@upstream_rules.delete_at(id)180end181182to_move.reverse.each do |rule|183@upstream_rules.insert(new_id, rule)184end185186to_move187end188189def flush190@upstream_rules.clear191end192193# The nameservers that match the given packet194# @param packet [Dnsruby::Message] The DNS packet to be sent195# @raise [ResolveError] If the packet contains multiple questions, which would end up sending to a different set of nameservers196# @return [Array<Array>] A list of nameservers, each with Rex::Socket options197#198def upstream_resolvers_for_packet(packet)199unless feature_set.enabled?(Msf::FeatureManager::DNS)200return super201end202# Leaky abstraction: a packet could have multiple question entries,203# and each of these could have different nameservers, or travel via204# different comm channels. We can't allow DNS leaks, so for now, we205# will throw an error here.206results_from_all_questions = []207packet.question.each do |question|208name = question.qname.to_s209upstream_rule = self.upstream_rules.find { |ur| ur.matches_name?(name) }210211if upstream_rule212upstream_resolvers = upstream_rule.resolvers213else214# Fall back to default nameservers215upstream_resolvers = super216end217results_from_all_questions << upstream_resolvers.uniq218end219results_from_all_questions.uniq!220if results_from_all_questions.size != 1221raise ResolverError.new('Inconsistent nameserver entries attempted to be sent in the one packet')222end223224results_from_all_questions[0]225end226227def self.extended(mod)228mod.init229end230231def set_framework(framework)232self.feature_set = framework.features233end234235def upstream_rules236@upstream_rules.dup237end238239private240241def load_config_entries242config = Msf::Config.load243244with_rules = []245config.fetch("#{CONFIG_KEY_BASE}/rules", {}).each do |_name, value|246wildcard, resolvers, uses_comm = value.split(';')247wildcard = '*' if wildcard.blank?248resolvers = resolvers.split(',')249uses_comm.downcase!250251raise Rex::Proto::DNS::Exceptions::ConfigError.new('DNS parsing failed: Comm must be true or false') unless ['true','false'].include?(uses_comm)252raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid upstream DNS resolver') unless resolvers.all? {|resolver| UpstreamRule.valid_resolver?(resolver) }253raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid rule') unless UpstreamRule.valid_wildcard?(wildcard)254255comm = uses_comm == 'true' ? CommSink.new : nil256with_rules << UpstreamRule.new(257wildcard: wildcard,258resolvers: resolvers,259comm: comm260)261end262263# Now that config has successfully read, update the global values264@upstream_rules = with_rules265end266267def load_config_static_hostnames268config = Msf::Config.load269270static_hostnames.flush271config.fetch("#{CONFIG_KEY_BASE}/static_hostnames", {}).each do |_name, value|272hostname, ip_addresses = value.split(';', 2)273ip_addresses.split(',').each do |ip_address|274next if ip_address.blank?275276unless Rex::Socket.is_ip_addr?(ip_address)277raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid IP address')278end279280static_hostnames.add(hostname, ip_address)281end282end283end284285def save_config_upstream_rules286new_config = {}287@upstream_rules.each_with_index do |entry, index|288val = [289entry.wildcard,290entry.resolvers.map do |resolver|291resolver.type == Rex::Proto::DNS::UpstreamResolver::Type::DNS_SERVER ? resolver.destination : resolver.type.to_s292end.join(','),293(!entry.comm.nil?).to_s294].join(';')295new_config["##{index}"] = val296end297Msf::Config.save("#{CONFIG_KEY_BASE}/rules" => new_config)298end299300def save_config_static_hostnames301new_config = {}302static_hostnames.each_with_index do |(hostname, addresses), index|303val = [304hostname,305(addresses.fetch(Dnsruby::Types::A, []) + addresses.fetch(Dnsruby::Types::AAAA, [])).join(',')306].join(';')307new_config["##{index}"] = val308end309Msf::Config.save("#{CONFIG_KEY_BASE}/static_hostnames" => new_config)310end311312attr_accessor :feature_set313end314end315end316end317318319