Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/dns/custom_nameserver_provider.rb
19670 views
1
require 'rex/proto/dns/upstream_resolver'
2
3
module Rex
4
module Proto
5
module DNS
6
7
##
8
# Provides a DNS resolver the ability to use different nameservers
9
# for different requests, based on the domain being queried.
10
##
11
module CustomNameserverProvider
12
CONFIG_KEY_BASE = 'framework/dns'
13
CONFIG_VERSION = Rex::Version.new('1.0')
14
15
#
16
# A Comm implementation that always reports as dead, so should never
17
# be used. This is used to prevent DNS leaks of saved DNS rules that
18
# were attached to a specific channel.
19
##
20
class CommSink
21
include Msf::Session::Comm
22
def alive?
23
false
24
end
25
26
def supports_udp?
27
# It won't be used anyway, so let's just say we support it
28
true
29
end
30
31
def sid
32
'previous MSF session'
33
end
34
end
35
36
def init
37
@upstream_rules = []
38
39
resolvers = [UpstreamResolver.create_static]
40
if @config[:nameservers].empty?
41
# if no nameservers are specified, fallback to the system
42
resolvers << UpstreamResolver.create_system
43
else
44
# migrate the originally configured name servers
45
resolvers += @config[:nameservers].map(&:to_s)
46
@config[:nameservers].clear
47
end
48
49
add_upstream_rule(resolvers)
50
51
nil
52
end
53
54
# Reinitialize the configuration to its original state.
55
def reinit
56
parse_config_file
57
parse_environment_variables
58
59
self.static_hostnames.flush
60
self.static_hostnames.parse_hosts_file
61
62
init
63
64
cache.flush if respond_to?(:cache)
65
66
nil
67
end
68
69
# Check whether or not there is configuration data in Metasploit's configuration file which is persisted on disk.
70
def has_config?
71
config = Msf::Config.load
72
version = config.fetch(CONFIG_KEY_BASE, {}).fetch('configuration_version', nil)
73
if version.nil?
74
@logger.info 'DNS configuration can not be loaded because the version is missing'
75
return false
76
end
77
78
their_version = Rex::Version.new(version)
79
if their_version > CONFIG_VERSION # if the config is newer, it's incompatible (we only guarantee backwards compat)
80
@logger.info "DNS configuration version #{their_version} can not be loaded because it is too new"
81
return false
82
end
83
84
my_minimum_version = Rex::Version.new(CONFIG_VERSION.canonical_segments.first.to_s)
85
if their_version < my_minimum_version # can not be older than our major version
86
@logger.info "DNS configuration version #{their_version} can not be loaded because it is too old"
87
return false
88
end
89
90
true
91
end
92
93
#
94
# Save the custom settings to the MSF config file
95
#
96
def save_config
97
new_config = {
98
'configuration_version' => CONFIG_VERSION.to_s
99
}
100
Msf::Config.save(CONFIG_KEY_BASE => new_config)
101
102
save_config_upstream_rules
103
save_config_static_hostnames
104
end
105
106
#
107
# Load the custom settings from the MSF config file
108
#
109
def load_config
110
unless has_config?
111
raise ResolverError.new('There is no compatible configuration data to load')
112
end
113
114
load_config_entries
115
load_config_static_hostnames
116
end
117
118
# Add a custom nameserver entry to the custom provider.
119
#
120
# @param [Array<String>] resolvers The list of upstream resolvers that would be used for this custom rule.
121
# @param [Msf::Session::Comm] comm The communication channel to be used for these DNS requests.
122
# @param [String] wildcard The wildcard rule to match a DNS request against.
123
# @param [Integer] index The index at which to insert the rule, defaults to -1 to append it at the end.
124
def add_upstream_rule(resolvers, comm: nil, wildcard: '*', index: -1)
125
resolvers = [resolvers] if resolvers.is_a?(String) # coerce into an array of strings
126
127
@upstream_rules.insert(index, UpstreamRule.new(
128
wildcard: wildcard,
129
resolvers: resolvers,
130
comm: comm
131
))
132
end
133
134
#
135
# Remove upstream rules with the given indices
136
# Ignore entries that are not found
137
# @param ids [Array<Integer>] The IDs to remove
138
# @return [Array<UpstreamRule>] The removed entries
139
def remove_ids(ids)
140
removed = []
141
ids.sort.reverse.each do |id|
142
upstream_rule = @upstream_rules.delete_at(id)
143
removed << upstream_rule if upstream_rule
144
end
145
146
removed.reverse
147
end
148
149
#
150
# Move upstream rules with the given indices into the location provided.
151
# If multiple IDs are provided, they will all be inserted into the provided location,
152
# in the order provided.
153
# Ignore entries that are not found
154
# @param ids [Array<Integer>] The IDs to move
155
# @param insertion_id [Integer] The ID to insert the entries at (in the order provided), or -1 to insert at the end
156
# @return [Array<UpstreamRule>] The moved entries
157
def reorder_ids(ids, new_id)
158
if new_id == -1
159
new_id = @upstream_rules.length
160
end
161
if new_id > @upstream_rules.length
162
raise ::ArgumentError.new("Insertion ID is past the end of the ruleset")
163
end
164
to_move = []
165
to_subtract = 0
166
# Get the entries before we delete (gets too complicated with indices changing otherwise)
167
ids.each do |id|
168
upstream_rule = @upstream_rules[id]
169
unless upstream_rule.nil?
170
to_move << upstream_rule
171
if new_id > id
172
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
173
end
174
end
175
end
176
177
new_id -= to_subtract
178
179
ids.sort.reverse.each do |id|
180
@upstream_rules.delete_at(id)
181
end
182
183
to_move.reverse.each do |rule|
184
@upstream_rules.insert(new_id, rule)
185
end
186
187
to_move
188
end
189
190
def flush
191
@upstream_rules.clear
192
end
193
194
# The nameservers that match the given packet
195
# @param packet [Dnsruby::Message] The DNS packet to be sent
196
# @raise [ResolveError] If the packet contains multiple questions, which would end up sending to a different set of nameservers
197
# @return [Array<Array>] A list of nameservers, each with Rex::Socket options
198
#
199
def upstream_resolvers_for_packet(packet)
200
unless feature_set.enabled?(Msf::FeatureManager::DNS)
201
return super
202
end
203
# Leaky abstraction: a packet could have multiple question entries,
204
# and each of these could have different nameservers, or travel via
205
# different comm channels. We can't allow DNS leaks, so for now, we
206
# will throw an error here.
207
results_from_all_questions = []
208
packet.question.each do |question|
209
name = question.qname.to_s
210
upstream_rule = self.upstream_rules.find { |ur| ur.matches_name?(name) }
211
212
if upstream_rule
213
upstream_resolvers = upstream_rule.resolvers
214
else
215
# Fall back to default nameservers
216
upstream_resolvers = super
217
end
218
results_from_all_questions << upstream_resolvers.uniq
219
end
220
results_from_all_questions.uniq!
221
if results_from_all_questions.size != 1
222
raise ResolverError.new('Inconsistent nameserver entries attempted to be sent in the one packet')
223
end
224
225
results_from_all_questions[0]
226
end
227
228
def self.extended(mod)
229
mod.init
230
end
231
232
def set_framework(framework)
233
self.feature_set = framework.features
234
end
235
236
def upstream_rules
237
@upstream_rules.dup
238
end
239
240
private
241
242
def load_config_entries
243
config = Msf::Config.load
244
245
with_rules = []
246
config.fetch("#{CONFIG_KEY_BASE}/rules", {}).each do |_name, value|
247
wildcard, resolvers, uses_comm = value.split(';')
248
wildcard = '*' if wildcard.blank?
249
resolvers = resolvers.split(',')
250
uses_comm.downcase!
251
252
raise Rex::Proto::DNS::Exceptions::ConfigError.new('DNS parsing failed: Comm must be true or false') unless ['true','false'].include?(uses_comm)
253
raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid upstream DNS resolver') unless resolvers.all? {|resolver| UpstreamRule.valid_resolver?(resolver) }
254
raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid rule') unless UpstreamRule.valid_wildcard?(wildcard)
255
256
comm = uses_comm == 'true' ? CommSink.new : nil
257
with_rules << UpstreamRule.new(
258
wildcard: wildcard,
259
resolvers: resolvers,
260
comm: comm
261
)
262
end
263
264
# Now that config has successfully read, update the global values
265
@upstream_rules = with_rules
266
end
267
268
def load_config_static_hostnames
269
config = Msf::Config.load
270
271
static_hostnames.flush
272
config.fetch("#{CONFIG_KEY_BASE}/static_hostnames", {}).each do |_name, value|
273
hostname, ip_addresses = value.split(';', 2)
274
ip_addresses.split(',').each do |ip_address|
275
next if ip_address.blank?
276
277
unless Rex::Socket.is_ip_addr?(ip_address)
278
raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid IP address')
279
end
280
281
static_hostnames.add(hostname, ip_address)
282
end
283
end
284
end
285
286
def save_config_upstream_rules
287
new_config = {}
288
@upstream_rules.each_with_index do |entry, index|
289
val = [
290
entry.wildcard,
291
entry.resolvers.map do |resolver|
292
resolver.type == Rex::Proto::DNS::UpstreamResolver::Type::DNS_SERVER ? resolver.destination : resolver.type.to_s
293
end.join(','),
294
(!entry.comm.nil?).to_s
295
].join(';')
296
new_config["##{index}"] = val
297
end
298
Msf::Config.save("#{CONFIG_KEY_BASE}/rules" => new_config)
299
end
300
301
def save_config_static_hostnames
302
new_config = {}
303
static_hostnames.each_with_index do |(hostname, addresses), index|
304
val = [
305
hostname,
306
(addresses.fetch(Dnsruby::Types::A, []) + addresses.fetch(Dnsruby::Types::AAAA, [])).join(',')
307
].join(';')
308
new_config["##{index}"] = val
309
end
310
Msf::Config.save("#{CONFIG_KEY_BASE}/static_hostnames" => new_config)
311
end
312
313
attr_accessor :feature_set
314
end
315
end
316
end
317
end
318
319