Module: Rex::Proto::DNS::CustomNameserverProvider

Defined in:
lib/rex/proto/dns/custom_nameserver_provider.rb

Overview

Provides a DNS resolver the ability to use different nameservers for different requests, based on the domain being queried.

Defined Under Namespace

Classes: CommSink

Constant Summary collapse

CONFIG_KEY_BASE =
'framework/dns'
CONFIG_VERSION =
Rex::Version.new('1.0')

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(mod) ⇒ Object



228
229
230
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 228

def self.extended(mod)
  mod.init
end

Instance Method Details

#add_upstream_rule(resolvers, comm: nil, wildcard: '*', index: -1)) ⇒ Object

Add a custom nameserver entry to the custom provider.

Parameters:

  • resolvers (Array<String>)

    The list of upstream resolvers that would be used for this custom rule.

  • comm (Msf::Session::Comm) (defaults to: nil)

    The communication channel to be used for these DNS requests.

  • wildcard (String) (defaults to: '*')

    The wildcard rule to match a DNS request against.

  • index (Integer) (defaults to: -1))

    The index at which to insert the rule, defaults to -1 to append it at the end.



124
125
126
127
128
129
130
131
132
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 124

def add_upstream_rule(resolvers, comm: nil, wildcard: '*', index: -1)
  resolvers = [resolvers] if resolvers.is_a?(String) # coerce into an array of strings

  @upstream_rules.insert(index, UpstreamRule.new(
    wildcard: wildcard,
    resolvers: resolvers,
    comm: comm
  ))
end

#flushObject



190
191
192
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 190

def flush
  @upstream_rules.clear
end

#has_config?Boolean

Check whether or not there is configuration data in Metasploit’s configuration file which is persisted on disk.

Returns:

  • (Boolean)


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 70

def has_config?
  config = Msf::Config.load
  version = config.fetch(CONFIG_KEY_BASE, {}).fetch('configuration_version', nil)
  if version.nil?
    @logger.info 'DNS configuration can not be loaded because the version is missing'
    return false
  end

  their_version = Rex::Version.new(version)
  if their_version > CONFIG_VERSION # if the config is newer, it's incompatible (we only guarantee backwards compat)
    @logger.info "DNS configuration version #{their_version} can not be loaded because it is too new"
    return false
  end

  my_minimum_version = Rex::Version.new(CONFIG_VERSION.canonical_segments.first.to_s)
  if their_version < my_minimum_version # can not be older than our major version
    @logger.info "DNS configuration version #{their_version} can not be loaded because it is too old"
    return false
  end

  true
end

#initObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 36

def init
  @upstream_rules = []

  resolvers = [UpstreamResolver.create_static]
  if @config[:nameservers].empty?
    # if no nameservers are specified, fallback to the system
    resolvers << UpstreamResolver.create_system
  else
    # migrate the originally configured name servers
    resolvers += @config[:nameservers].map(&:to_s)
    @config[:nameservers].clear
  end

  add_upstream_rule(resolvers)

  nil
end

#load_configObject

Load the custom settings from the MSF config file



109
110
111
112
113
114
115
116
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 109

def load_config
  unless has_config?
    raise ResolverError.new('There is no compatible configuration data to load')
  end

  load_config_entries
  load_config_static_hostnames
end

#reinitObject

Reinitialize the configuration to its original state.



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 55

def reinit
  parse_config_file
  parse_environment_variables

  self.static_hostnames.flush
  self.static_hostnames.parse_hosts_file

  init

  cache.flush if respond_to?(:cache)

  nil
end

#remove_ids(ids) ⇒ Array<UpstreamRule>

Remove upstream rules with the given indices Ignore entries that are not found

Parameters:

  • ids (Array<Integer>)

    The IDs to remove

Returns:



139
140
141
142
143
144
145
146
147
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 139

def remove_ids(ids)
  removed = []
  ids.sort.reverse.each do |id|
    upstream_rule = @upstream_rules.delete_at(id)
    removed << upstream_rule if upstream_rule
  end

  removed.reverse
end

#reorder_ids(ids, new_id) ⇒ Array<UpstreamRule>

Move upstream rules with the given indices into the location provided. If multiple IDs are provided, they will all be inserted into the provided location, in the order provided. Ignore entries that are not found

Parameters:

  • ids (Array<Integer>)

    The IDs to move

  • insertion_id (Integer)

    The ID to insert the entries at (in the order provided), or -1 to insert at the end

Returns:



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 157

def reorder_ids(ids, new_id)
  if new_id == -1
    new_id = @upstream_rules.length
  end
  if new_id > @upstream_rules.length
    raise ::ArgumentError.new("Insertion ID is past the end of the ruleset")
  end
  to_move = []
  to_subtract = 0
  # Get the entries before we delete (gets too complicated with indices changing otherwise)
  ids.each do |id|
    upstream_rule = @upstream_rules[id]
    unless upstream_rule.nil?
      to_move << upstream_rule
      if new_id > id
        to_subtract += 1 # Adjust for the fact that are about to delete one, so the indices would be off-by-one after that index is deleted
      end
    end
  end

  new_id -= to_subtract

  ids.sort.reverse.each do |id|
    @upstream_rules.delete_at(id)
  end

  to_move.reverse.each do |rule|
    @upstream_rules.insert(new_id, rule)
  end

  to_move
end

#save_configObject

Save the custom settings to the MSF config file



96
97
98
99
100
101
102
103
104
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 96

def save_config
  new_config = {
    'configuration_version' => CONFIG_VERSION.to_s
  }
  Msf::Config.save(CONFIG_KEY_BASE => new_config)

  save_config_upstream_rules
  save_config_static_hostnames
end

#set_framework(framework) ⇒ Object



232
233
234
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 232

def set_framework(framework)
  self.feature_set = framework.features
end

#upstream_resolvers_for_packet(packet) ⇒ Array<Array>

The nameservers that match the given packet

Parameters:

  • packet (Dnsruby::Message)

    The DNS packet to be sent

Returns:

  • (Array<Array>)

    A list of nameservers, each with Rex::Socket options

Raises:

  • (ResolveError)

    If the packet contains multiple questions, which would end up sending to a different set of nameservers



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 199

def upstream_resolvers_for_packet(packet)
  unless feature_set.enabled?(Msf::FeatureManager::DNS)
    return super
  end
  # Leaky abstraction: a packet could have multiple question entries,
  # and each of these could have different nameservers, or travel via
  # different comm channels. We can't allow DNS leaks, so for now, we
  # will throw an error here.
  results_from_all_questions = []
  packet.question.each do |question|
    name = question.qname.to_s
    upstream_rule = self.upstream_rules.find { |ur| ur.matches_name?(name) }

    if upstream_rule
      upstream_resolvers = upstream_rule.resolvers
    else
      # Fall back to default nameservers
      upstream_resolvers = super
    end
    results_from_all_questions << upstream_resolvers.uniq
  end
  results_from_all_questions.uniq!
  if results_from_all_questions.size != 1
    raise ResolverError.new('Inconsistent nameserver entries attempted to be sent in the one packet')
  end

  results_from_all_questions[0]
end

#upstream_rulesObject



236
237
238
# File 'lib/rex/proto/dns/custom_nameserver_provider.rb', line 236

def upstream_rules
  @upstream_rules.dup
end