From bd9891dc4ddc198ddb70ee05af4bb412ede546c6 Mon Sep 17 00:00:00 2001 From: soh_cah_toa Date: Wed, 23 Apr 2014 16:39:26 -0400 Subject: [PATCH] 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. --- extensions/dns/model.rb | 228 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/extensions/dns/model.rb b/extensions/dns/model.rb index f4b9aef98..87d734a16 100644 --- a/extensions/dns/model.rb +++ b/extensions/dns/model.rb @@ -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!('%s', '%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('%s'), " + + "Resolv::DNS::Name.create('%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!(%d, Resolv::DNS::Name.create('%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('%s'), " + + "Resolv::DNS::Name.create('%s'), " + + '%d, ' + + '%d, ' + + '%d, ' + + '%d, ' + + '%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!('%
s', %d, %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