CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/dns/custom_nameserver_provider.rb
Views: 11704
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 indexes
136
# Ignore entries that are not found
137
# @param ids [Array<Integer>] The IDs to removed
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
def flush
150
@upstream_rules.clear
151
end
152
153
# The nameservers that match the given packet
154
# @param packet [Dnsruby::Message] The DNS packet to be sent
155
# @raise [ResolveError] If the packet contains multiple questions, which would end up sending to a different set of nameservers
156
# @return [Array<Array>] A list of nameservers, each with Rex::Socket options
157
#
158
def upstream_resolvers_for_packet(packet)
159
unless feature_set.enabled?(Msf::FeatureManager::DNS)
160
return super
161
end
162
# Leaky abstraction: a packet could have multiple question entries,
163
# and each of these could have different nameservers, or travel via
164
# different comm channels. We can't allow DNS leaks, so for now, we
165
# will throw an error here.
166
results_from_all_questions = []
167
packet.question.each do |question|
168
name = question.qname.to_s
169
upstream_rule = self.upstream_rules.find { |ur| ur.matches_name?(name) }
170
171
if upstream_rule
172
upstream_resolvers = upstream_rule.resolvers
173
else
174
# Fall back to default nameservers
175
upstream_resolvers = super
176
end
177
results_from_all_questions << upstream_resolvers.uniq
178
end
179
results_from_all_questions.uniq!
180
if results_from_all_questions.size != 1
181
raise ResolverError.new('Inconsistent nameserver entries attempted to be sent in the one packet')
182
end
183
184
results_from_all_questions[0]
185
end
186
187
def self.extended(mod)
188
mod.init
189
end
190
191
def set_framework(framework)
192
self.feature_set = framework.features
193
end
194
195
def upstream_rules
196
@upstream_rules.dup
197
end
198
199
private
200
201
def load_config_entries
202
config = Msf::Config.load
203
204
with_rules = []
205
config.fetch("#{CONFIG_KEY_BASE}/rules", {}).each do |_name, value|
206
wildcard, resolvers, uses_comm = value.split(';')
207
wildcard = '*' if wildcard.blank?
208
resolvers = resolvers.split(',')
209
uses_comm.downcase!
210
211
raise Rex::Proto::DNS::Exceptions::ConfigError.new('DNS parsing failed: Comm must be true or false') unless ['true','false'].include?(uses_comm)
212
raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid upstream DNS resolver') unless resolvers.all? {|resolver| UpstreamRule.valid_resolver?(resolver) }
213
raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid rule') unless UpstreamRule.valid_wildcard?(wildcard)
214
215
comm = uses_comm == 'true' ? CommSink.new : nil
216
with_rules << UpstreamRule.new(
217
wildcard: wildcard,
218
resolvers: resolvers,
219
comm: comm
220
)
221
end
222
223
# Now that config has successfully read, update the global values
224
@upstream_rules = with_rules
225
end
226
227
def load_config_static_hostnames
228
config = Msf::Config.load
229
230
static_hostnames.flush
231
config.fetch("#{CONFIG_KEY_BASE}/static_hostnames", {}).each do |_name, value|
232
hostname, ip_addresses = value.split(';', 2)
233
ip_addresses.split(',').each do |ip_address|
234
next if ip_address.blank?
235
236
unless Rex::Socket.is_ip_addr?(ip_address)
237
raise Rex::Proto::DNS::Exceptions::ConfigError.new('Invalid DNS config: Invalid IP address')
238
end
239
240
static_hostnames.add(hostname, ip_address)
241
end
242
end
243
end
244
245
def save_config_upstream_rules
246
new_config = {}
247
@upstream_rules.each_with_index do |entry, index|
248
val = [
249
entry.wildcard,
250
entry.resolvers.map do |resolver|
251
resolver.type == Rex::Proto::DNS::UpstreamResolver::Type::DNS_SERVER ? resolver.destination : resolver.type.to_s
252
end.join(','),
253
(!entry.comm.nil?).to_s
254
].join(';')
255
new_config["##{index}"] = val
256
end
257
Msf::Config.save("#{CONFIG_KEY_BASE}/rules" => new_config)
258
end
259
260
def save_config_static_hostnames
261
new_config = {}
262
static_hostnames.each_with_index do |(hostname, addresses), index|
263
val = [
264
hostname,
265
(addresses.fetch(Dnsruby::Types::A, []) + addresses.fetch(Dnsruby::Types::AAAA, [])).join(',')
266
].join(';')
267
new_config["##{index}"] = val
268
end
269
Msf::Config.save("#{CONFIG_KEY_BASE}/static_hostnames" => new_config)
270
end
271
272
attr_accessor :feature_set
273
end
274
end
275
end
276
end
277
278