Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb
Views: 11783
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'bindata'67class MetasploitModule < Msf::Auxiliary8include Msf::Exploit::Remote::Udp9include Msf::Exploit::Remote::Tcp10include Msf::Auxiliary::Fuzzer11include Msf::Auxiliary::Scanner1213def initialize14super(15'Name' => 'DNS and DNSSEC Fuzzer',16'Description' => %q{17This module will connect to a DNS server and perform DNS and18DNSSEC protocol-level fuzzing. Note that this module may inadvertently19crash the target server.20},21'Author' => [ 'pello <fropert[at]packetfault.org>' ],22'License' => MSF_LICENSE23)2425register_options([26Opt::RPORT(53),27OptInt.new('STARTSIZE', [ false, "Fuzzing string startsize.",0]),28OptInt.new('ENDSIZE', [ false, "Max Fuzzing string size. (L2 Frame size)",500]),29OptInt.new('STEPSIZE', [ false, "Increment fuzzing string each attempt.",100]),30OptInt.new('ERRORHDR', [ false, "Introduces byte error in the DNS header.", 0]),31OptBool.new('CYCLIC', [ false, "Use Cyclic pattern instead of A's (fuzzing payload).",true]),32OptInt.new("ITERATIONS", [true, "Number of iterations to run by test case", 5]),33OptString.new('DOMAIN', [ false, "Force DNS zone domain name."]),34OptString.new('IMPORTENUM', [ false, "Import dns_enum database output and automatically use existing RR."]),35OptEnum.new('METHOD', [false, 'Underlayer protocol to use', 'UDP', ['UDP', 'TCP', 'AUTO']]),36OptBool.new('DNSSEC', [ false, "Add DNSsec to each question (UDP payload size, EDNS0, ...)",false]),37OptBool.new('TRAILINGNUL', [ false, "NUL byte terminate DNS names",true]),38OptBool.new('RAWPADDING', [ false, "Generate totally random data from STARTSIZE to ENDSIZE",false]),39OptString.new('OPCODE', [ false, "Comma separated list of opcodes to fuzz. Leave empty to fuzz all fields.",'' ]),40# OPCODE accepted values: QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE41OptString.new('CLASS', [ false, "Comma separated list of classes to fuzz. Leave empty to fuzz all fields.",'' ]),42# CLASS accepted values: IN,CH,HS,NONE,ANY43OptString.new('RR', [ false, "Comma separated list of requests to fuzz. Leave empty to fuzz all fields.",'' ])44# RR accepted values: A,CNAME,MX,PTR,TXT,AAAA,HINFO,SOA,NS,WKS,RRSIG,DNSKEY,DS,NSEC,NSEC3,NSEC3PARAM45# RR accepted values: AFSDB,ISDN,RP,RT,X25,PX,SRV,NAPTR,MD,MF,MB,MG,MR,NULL,MINFO,NSAP,NSAP-PTR,SIG46# RR accepted values: KEY,GPOS,LOC,NXT,EID,NIMLOC,ATMA,KX,CERT,A6,DNAME,SINK,OPT,APL,SSHFP,IPSECKEY47# RR accepted values: DHCID,HIP,NINFO,RKEY,TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG,IXFR,AXFR,MAILB48# RR accepted values: MAIL,*,TA,DLV,RESERVED49])50end5152class Dns_header < BinData::Record53endian :big54uint16 :txid, initial_value: rand(0xffff)55bit1 :qr56bit4 :opcode57bit1 :aa58bit1 :tc59bit1 :rd60bit1 :ra61bit3 :z62bit4 :rcode63uint16 :questions, initial_value: 164uint16 :answerRR65uint16 :authorityRR66uint16 :additionalRR67rest :payload68end6970class Dns_add_rr < BinData::Record71endian :big72uint8 :name73uint16 :rr_type, initial_value: 0x002974uint16 :payloadsize, initial_value: 0x100075uint8 :highercode76uint8 :ednsversion77uint8 :zlow78uint8 :zhigh, initial_value: 0x8079uint16 :datalength80end8182def msg83"#{rhost}:#{rport} - DNS -"84end8586def check_response_construction(pkt)87# check if RCODE is not in the unassigned/reserved range88if pkt[4].to_i >= 0x17 || (pkt[4].to_i >= 0x0b && pkt[4].to_i <= 0x0f)89print_error("#{msg} Server replied incorrectly to the following request:\n#{@lastdata.unpack('H*')}")90return false91else92return true93end94end9596def dns_alive(method)97connect_udp if method == "UDP" || method == "AUTO"98connect if method == "TCP"99100payload = ""101domain = ""102if @domain == nil103domain << Rex::Text.rand_text_alphanumeric(rand(2)+2)104domain << "."105domain << Rex::Text.rand_text_alphanumeric(rand(6)+3)106domain << "."107domain << Rex::Text.rand_text_alphanumeric(2)108else109domain << Rex::Text.rand_text_alphanumeric(rand(2)+2)110domain << "."111domain << @domain112end113114splitFQDN = domain.split('.')115payload = splitFQDN.inject("") { |a,x| a + [x.length,x].pack("CA*") }116pkt = Dns_header.new117pkt.txid = rand(0xffff)118pkt.opcode = 0x0000119pkt.payload = payload + "\x00" + "\x00\x01" + "\x00\x01"120testingPkt = pkt.to_binary_s121122if method == "UDP"123udp_sock.put(testingPkt)124res, addr = udp_sock.recvfrom(65535)125disconnect_udp126elsif method == "TCP"127sock.put(testingPkt)128res, addr = sock.get_once(-1, 20)129disconnect130end131132if res && res.empty?133print_error("#{msg} The remote server is not responding to DNS requests.")134return false135else136return true137end138end139140def fuzz_padding(payload, size)141padding = size - payload.length142if padding <= 0 then return payload end143if datastore['CYCLIC']144@fuzzdata = Rex::Text.rand_text_alphanumeric(padding)145else146@fuzzdata = 'A' * padding147end148payload = payload.ljust(padding, @fuzzdata)149return payload150end151152def corrupt_header(pkt,nb)153len = pkt.length - 1154for i in 0..nb - 1155selectByte = rand(len)156pkt[selectByte] = [rand(255).to_s].pack('H')157end158return pkt159end160161def random_payload(size)162pkt = Array.new163for i in 0..size - 1164pkt[i] = [rand(255).to_s].pack('H')165end166return pkt167end168169def setup_fqdn(domain,entry)170if domain == nil171domain = ""172domain << Rex::Text.rand_text_alphanumeric(rand(62)+2)173domain << "."174domain << Rex::Text.rand_text_alphanumeric(rand(61)+3)175domain << "."176domain << Rex::Text.rand_text_alphanumeric(rand(62)+2)177elsif @dnsfile178domain = entry + "." + domain179else180domain = Rex::Text.rand_text_alphanumeric(rand(62)+2) + "." + domain181end182return domain183end184185def import_enum_data(dnsfile)186enumdata = Array.new(count = File.foreach(dnsfile).inject(0) {|c, line| c+1}, 0)187idx = 0188File.open(dnsfile,"rb").each_line do |line|189line = line.split(",")190enumdata[idx] = Hash.new191enumdata[idx][:name] = line[0].strip192enumdata[idx][:rr] = line[1].strip193enumdata[idx][:class] = line[2].strip194idx = idx + 1195end196return enumdata197end198199def setup_nsclass(nsclass)200classns = ""201for idx in nsclass202classns << {203"IN" => 0x0001, "CH" => 0x0003, "HS" => 0x0004,204"NONE" => 0x00fd, "ANY" => 0x00ff205}.values_at(idx).pack("n")206end207return classns208end209210def setup_opcode(nsopcode)211opcode = ""212for idx in nsopcode213opcode << {214"QUERY" => 0x0000, "IQUERY" => 0x0001, "STATUS" => 0x0002,215"UNASSIGNED" => 0x0003, "NOTIFY" => 0x0004, "UPDATE" => 0x0005216}.values_at(idx).pack("n")217end218return opcode219end220221def setup_reqns(nsreq)222reqns= ""223for idx in nsreq224reqns << {225"A" => 0x0001, "NS" => 0x0002, "MD" => 0x0003, "MF" => 0x0004,226"CNAME" => 0x0005, "SOA" => 0x0006, "MB" => 0x0007, "MG" => 0x0008,227"MR" => 0x0009, "NULL" => 0x000a, "WKS" => 0x000b, "PTR" => 0x000c,228"HINFO" => 0x000d, "MINFO" => 0x000e, "MX" => 0x000f, "TXT" => 0x0010,229"RP" => 0x0011, "AFSDB" => 0x0012, "X25" => 0x0013, "ISDN" => 0x0014,230"RT" => 0x0015, "NSAP" => 0x0016, "NSAP-PTR" => 0x0017, "SIG" => 0x0018,231"KEY" => 0x0019, "PX" => 0x001a, "GPOS" => 0x001b, "AAAA" => 0x001c,232"LOC" => 0x001d, "NXT" => 0x001e, "EID" => 0x001f, "NIMLOC" => 0x0020,233"SRV" => 0x0021, "ATMA" => 0x0022, "NAPTR" => 0x0023, "KX" => 0x0024,234"CERT" => 0x0025, "A6" => 0x0026, "DNAME" => 0x0027, "SINK" => 0x0028,235"OPT" => 0x0029, "APL" => 0x002a, "DS" => 0x002b, "SSHFP" => 0x002c,236"IPSECKEY" => 0x002d, "RRSIG" => 0x002e, "NSEC" => 0x002f, "DNSKEY" => 0x0030,237"DHCID" => 0x0031, "NSEC3" => 0x0032, "NSEC3PARAM" => 0x0033, "HIP" => 0x0037,238"NINFO" => 0x0038, "RKEY" => 0x0039, "TALINK" => 0x003a, "SPF" => 0x0063,239"UINFO" => 0x0064, "UID" => 0x0065, "GID" => 0x0066, "UNSPEC" => 0x0067,240"TKEY" => 0x00f9, "TSIG" => 0x00fa, "IXFR" => 0x00fb, "AXFR" => 0x00fc,241"MAILA" => 0x00fd, "MAILB" => 0x00fe, "*" => 0x00ff, "TA" => 0x8000,242"DLV" => 0x8001, "RESERVED" => 0xffff243}.values_at(idx).pack("n")244end245return reqns246end247248def build_packet(dnsOpcode,dnssec,trailingnul,reqns,classns,payload)249pkt = Dns_header.new250pkt.opcode = dnsOpcode251if trailingnul252if @dnsfile253pkt.payload = payload + "\x00" + reqns + classns254else255pkt.payload = payload + "\x00" + [reqns].pack("n") + [classns].pack("n")256end257else258if @dnsfile259pkt.payload = payload + [(rand(255) + 1).to_s].pack('H') + reqns + classns260else261pkt.payload = payload + [(rand(255) + 1).to_s].pack('H') + [dnsReq].pack("n") + [dnsClass].pack("n")262end263end264if dnssec265dnssecpkt = Dns_add_rr.new266pkt.additionalRR = 1267pkt.payload = dnssecpkt.to_binary_s268end269return pkt.to_binary_s270end271272def dns_send(data,method)273method = "UDP" if (method == "AUTO" && data.length < 512)274method = "TCP" if (method == "AUTO" && data.length >= 512)275276connect_udp if method == "UDP"277connect if method == "TCP"278udp_sock.put(data) if method == "UDP"279sock.put(data) if method == "TCP"280281res, addr = udp_sock.recvfrom(65535,1) if method == "UDP"282res, addr = sock.get_once(-1,1) if method == "TCP"283284disconnect_udp if method == "UDP"285disconnect if method == "TCP"286287if res && res.length == 0288@failCount += 1289if @failCount == 1290@probablyVuln = @lastdata if @lastdata != nil291return true292elsif @failCount >= 3293if dns_alive(method) == false294if @lastdata295print_error("#{msg} DNS is DOWN since the request:")296print_error(lastdata.unpack('H*'))297else298print_error("#{msg} DNS is DOWN")299end300return false301else302return true303end304else305return true306end307elsif res && res.length > 0308@lastdata = data309if res[3].to_i >= 0x8000 # ignore server response as a query310@failCount = 0311return true312end313if @rawpadding314@failCount = 0315return true316end317if check_response_construction(res)318@failCount = 0319return true320else321return false322end323end324end325326def fix_variables327@fuzz_opcode = datastore['OPCODE'].blank? ? "QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE" : datastore['OPCODE']328@fuzz_class = datastore['CLASS'].blank? ? "IN,CH,HS,NONE,ANY" : datastore['CLASS']329fuzz_rr_queries = "A,NS,MD,MF,CNAME,SOA,MB,MG,MR,NULL,WKS,PTR," <<330"HINFO,MINFO,MX,TXT,RP,AFSDB,X25,ISDN,RT," <<331"NSAP,NSAP-PTR,SIG,KEY,PX,GPOS,AAAA,LOC,NXT," <<332"EID,NIMLOC,SRV,ATMA,NAPTR,KX,CERT,A6,DNAME," <<333"SINK,OPT,APL,DS,SSHFP,IPSECKEY,RRSIG,NSEC," <<334"DNSKEY,DHCID,NSEC3,NSEC3PARAM,HIP,NINFO,RKEY," <<335"TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG," <<336"IXFR,AXFR,MAILA,MAILB,*,TA,DLV,RESERVED"337@fuzz_rr = datastore['RR'].blank? ? fuzz_rr_queries : datastore['RR']338end339340def run_host(ip)341msg = "#{ip}:#{rhost} - DNS -"342begin343@lastdata = nil344@probablyVuln = nil345@startsize = datastore['STARTSIZE']346@stepsize = datastore['STEPSIZE']347@endsize = datastore['ENDSIZE']348@underlayerProtocol = datastore['METHOD']349@failCount = 0350@domain = datastore['DOMAIN']351@dnsfile = datastore['IMPORTENUM']352@rawpadding = datastore['RAWPADDING']353iter = datastore['ITERATIONS']354dnssec = datastore['DNSSEC']355errorhdr = datastore['ERRORHDR']356trailingnul = datastore['TRAILINGNUL']357358fix_variables359360if !dns_alive(@underlayerProtocol) then return false end361362print_status("#{msg} Fuzzing DNS server, this may take a while.")363364if @startsize < 12 && @startsize > 0365print_status("#{msg} STARTSIZE must be at least 12. STARTSIZE value has been modified.")366@startsize = 12367end368369if @rawpadding370if @domain == nil371print_status("DNS Fuzzer: DOMAIN could be set for health check but not mandatory.")372end373nsopcode=@fuzz_opcode.split(",")374opcode = setup_opcode(nsopcode)375opcode.unpack("n*").each do |dnsOpcode|3761.upto(iter) do377while @startsize <= @endsize378data = random_payload(@startsize).to_s379data[2] = 0x0380data[3] = dnsOpcode381if !dns_send(data,@underlayerProtocol) then return false end382@lastdata = data383@startsize += @stepsize384end385@startsize = datastore['STARTSIZE']386end387end388return389end390391if @dnsfile392if @domain == nil393print_error("DNS Fuzzer: Domain variable must be set.")394return395end396397dnsenumdata = import_enum_data(@dnsfile)398nsreq = []399nsclass = []400nsentry = []401for req, value in dnsenumdata402nsreq << req[:rr]403nsclass << req[:class]404nsentry << req[:name]405end406nsopcode=@fuzz_opcode.split(",")407else408nsreq=@fuzz_rr.split(",")409nsopcode=@fuzz_opcode.split(",")410nsclass=@fuzz_class.split(",")411begin412classns = setup_nsclass(nsclass)413raise ArgumentError, "Invalid CLASS: #{nsclass.inspect}" unless classns414opcode = setup_opcode(nsopcode)415raise ArgumentError, "Invalid OPCODE: #{opcode.inspect}" unless nsopcode416reqns = setup_reqns(nsreq)417raise ArgumentError, "Invalid RR: #{nsreq.inspect}" unless nsreq418rescue ::Exception => e419print_error("DNS Fuzzer error, aborting: #{e}")420return421end422end423424for question in nsreq425case question426when "RRSIG", "DNSKEY", "DS", "NSEC", "NSEC3", "NSEC3PARAM"427dnssec = true428end429end430431if @dnsfile432classns = setup_nsclass(nsclass)433reqns = setup_reqns(nsreq)434opcode = setup_opcode(nsopcode)435opcode.unpack("n*").each do |dnsOpcode|436for i in 0..nsentry.length - 1437reqns = setup_reqns(nsreq[i])438classns = setup_nsclass(nsclass[i])4391.upto(iter) do440payload = ""441nsdomain = setup_fqdn(@domain,nsentry[i])442splitFQDN = nsdomain.split('.')443payload = splitFQDN.inject("") { |a,x| a + [x.length,x].pack("CA*") }444pkt = build_packet(dnsOpcode,dnssec,trailingnul,reqns,classns,payload)445pkt = corrupt_header(pkt,errorhdr) if errorhdr > 0446if @startsize == 0447if !dns_send(pkt,@underlayerProtocol) then return end448else449while @startsize <= @endsize450pkt = fuzz_padding(pkt, @startsize)451if !dns_send(pkt,@underlayerProtocol) then return end452@startsize += @stepsize453end454@startsize = datastore['STARTSIZE']455end456end457end458end459else460classns.unpack("n*").each do |dnsClass|461opcode.unpack("n*").each do |dnsOpcode|462reqns.unpack("n*").each do |dnsReq|4631.upto(iter) do464payload = ""465nsdomain = setup_fqdn(@domain,"")466splitFQDN = nsdomain.split('.')467payload = splitFQDN.inject("") { |a,x| a + [x.length,x].pack("CA*") }468pkt = build_packet(dnsOpcode,dnssec,trailingnul,dnsReq,dnsClass,payload)469pkt = corrupt_header(pkt,errorhdr) if errorhdr > 0470if @startsize == 0471if !dns_send(pkt,@underlayerProtocol) then return end # If then return end?472else473while @startsize <= @endsize474pkt = fuzz_padding(pkt, @startsize)475if !dns_send(pkt,@underlayerProtocol) then return end476@startsize += @stepsize477end478@startsize = datastore['STARTSIZE']479end480end481end482end483end484end485end486end487end488489490