Implemented #validate_response method in Dns::Rule.

Perhaps the ugliest part of the DNS extension, it is also the most
crucial. This method ensures that a given resource and response are
appropriate for each other. It must also prevent RCE vulns since the
input is eval'd later on.

However, HINFO, MINFO, and especially TXT validation is not strict
enough. These three need to be reviewed scrupulously since a 100%
anti-RCE solution may prove to be difficult.
This commit is contained in:
soh_cah_toa
2014-04-23 16:39:26 -04:00
parent 53a54de5fe
commit bd9891dc4d

View File

@@ -34,7 +34,233 @@ module BeEF
#
# @return [String] string representation of callback that can safely be eval'd
def validate_response(resource, response)
"t.respond!('1.1.1.1')"
domain_regex = /^[0-9a-z-]+(\.[0-9a-z-]+)*(\.[a-z]{2,})$/i
sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i
ipv4_regex = /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}
(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/x
ipv6_regex = /^(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}|
([0-9a-f]{1,4}:){1,7}:|
([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|
([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|
([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}|
([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|
([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}|
[0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})|
:((:[0-9a-f]{1,4}){1,7}|:)|
fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}|
::(ffff(:0{1,4}){0,1}:){0,1}
((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}
(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|
([0-9a-f]{1,4}:){1,4}:
((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}
(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ix
begin
src = if resource == Resolv::DNS::Resource::IN::A
if response.is_a?(String) && response =~ ipv4_regex
sprintf "t.respond!('%s')", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
elsif response.is_a?(Array)
str1 = "t.respond!('%s');"
str2 = ''
response.each do |r|
raise InvalidDnsResponseError, 'A' unless r =~ ipv4_regex
str2 << sprintf(str1, r)
end
str2
else
raise InvalidDnsResponseError, 'A'
end
elsif resource == Resolv::DNS::Resource::IN::AAAA
if response.is_a?(String) && response =~ ipv6_regex
sprintf "t.respond!('%s')", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
elsif response.is_a?(Array)
str1 = "t.respond!('%s');"
str2 = ''
response.each do |r|
raise InvalidDnsResponseError, 'AAAA' unless r =~ ipv6_regex
str2 << sprintf(str1, r)
end
str2
else
raise InvalidDnsResponseError, 'AAAA'
end
elsif resource == Resolv::DNS::Resource::IN::CNAME
if response.is_a?(String) && response =~ domain_regex
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'CNAME'
end
elsif resource == Resolv::DNS::Resource::IN::HINFO
if response.is_a?(Array)
response.each { |r| raise InvalidDnsResponseError, 'HINFO' unless r.is_a?(String) }
data = { :cpu => response[0], :os => response[1] }
sprintf "t.respond!('%<cpu>s', '%<os>s')", data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'HINFO'
end
elsif resource == Resolv::DNS::Resource::IN::MINFO
if response.is_a?(Array)
response.each { |r| raise InvalidDnsResponseError, 'MINFO' unless r.is_a?(String) && r =~ domain_regex }
data = { :rmailbx => response[0], :emailbx => response[1] }
sprintf "t.respond!(Resolv::DNS::Name.create('%<rmailbx>s'), " +
"Resolv::DNS::Name.create('%<emailbx>s'))",
data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'MINFO'
end
elsif resource == Resolv::DNS::Resource::IN::MX
if response[0].is_a?(Integer) &&
response[1].is_a?(String) &&
response[1] =~ domain_regex
data = { :preference => response[0], :exchange => response[1] }
sprintf "t.respond!(%<preference>d, Resolv::DNS::Name.create('%<exchange>s'))", data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'MX'
end
elsif resource == Resolv::DNS::Resource::IN::NS
if response.is_a?(String) && response =~ domain_regex
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
elsif response.is_a?(Array)
str1 = "t.respond!(Resolv::DNS::Name.create('%s'))"
str2 = ''
response.each do |r|
raise InvalidDnsResponseError, 'NS' unless r =~ ipv4_regex
str2 << sprintf(str1, r)
end
str2
else
raise InvalidDnsResponseError, 'NS'
end
elsif resource == Resolv::DNS::Resource::IN::PTR
if response.is_a?(String) && response =~ domain_regex
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'PTR'
end
elsif resource == Resolv::DNS::Resource::IN::SOA
if response.is_a?(Array)
unless response[0].is_a?(String) &&
response[0] =~ domain_regex &&
response[1].is_a?(String) &&
response[1] =~ domain_regex &&
response[2].is_a?(Integer) &&
response[3].is_a?(Integer) &&
response[4].is_a?(Integer) &&
response[5].is_a?(Integer) &&
response[6].is_a?(Integer)
raise InvalidDnsResponseError, 'SOA'
end
data = {
:mname => response[0],
:rname => response[1],
:serial => response[2],
:refresh => response[3],
:retry => response[4],
:expire => response[5],
:minimum => response[6]
}
sprintf "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
"Resolv::DNS::Name.create('%<rname>s'), " +
'%<serial>d, ' +
'%<refresh>d, ' +
'%<retry>d, ' +
'%<expire>d, ' +
'%<minimum>d)',
data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'SOA'
end
elsif resource == Resolv::DNS::Resource::IN::TXT
if resource.is_a?(String)
sprintf "t.respond!('%s')", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'TXT'
end
elsif resource == Resolv::DNS::Resource::IN::WKS
if response.is_a?(Array)
unless resource[0].is_a?(String) &&
resource[0] =~ ipv4_regex &&
resource[1].is_a?(Integer) &&
resource[2].is_a?(Integer)
raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String)
end
data = {
:address => response[0],
:protocol => response[1],
:bitmap => response[2]
}
sprintf "t.respond!('%<address>s', %<protocol>d, %<bitmap>d)", data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
else
raise InvalidDnsResponseError, 'WKS'
end
else
raise UnknownDnsResourceError
end
rescue InvalidDnsResponseError, UnknownDnsResourceError => e
print_error e.message
throw :halt
end
src
end
# Raised when a response is not valid for the given DNS resource record.
class InvalidDnsResponseError < StandardError
def initialize(message = nil)
message = sprintf "Invalid response specified for %s resource record", message unless message.nil?
super(message)
end
end
# Raised when an unknown DNS resource record is given.
class UnknownDnsResourceError < StandardError
DEFAULT_MESSAGE = 'Unknown resource record was given'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
end