Talos Vulnerability Report

TALOS-2016-0071

Network Time Protocol Skeleton Key: Symmetric Authentication Impersonation Vulnerability

January 19, 2016

Report IDs

CVE-2015-7974 (NTP, NTPsec)
CVE-2016-1567 (chrony)
CERT VU#357792

Summary

Symmetric key encryption requires a single trusted key to be specified for each server configuration. A key specified only for one server should only work to authenticate that server, other trusted keys should be refused.

Instead we observe that when symmetric key authentication is verified, there is no check that the key used is the key specified for the address, any trusted key can be used as long as the keyid references another key the systems share and that key is used to compute the MAC.

This has three implications for the client server model. A client that has multiple servers configured each with different keys could be attacked by one of its servers spoofing every other server using its own key. Even worse a server can be attacked by any of its authenticated clients in a similar manner.

Finally, being able to use any key to authenticate a packet for a client or server means that if any key in the trustedkeys list uses a weak digest algorithim (MD5), then an attacker can abuse that method instead of being restricted by the stronger keys configured.

NOTE: Code locations referenced below refer to the NTP reference implementation from http://www.ntp.org.

There is no clear location in the code where this defect occurs, since it exists due to an omission. Verifying the key used matches the proper server’s key could be done in ntp_proto.c around line 803 (in 4.8.2p3) where authdecrypt is called, or it might make sense to build it into the libntp code such as the authdecrypt function itself.

Be aware that this issue could affect other ntpd modes of operation such as broadcast or active/passive peering.

Tested Versions

NTP 4.2.8p3
NTPsec a5fb34b9cc89b92a8fef2f459004865c93bb7f92
chrony 2.2

Product URLs

http://www.ntp.org
http://www.ntpsec.org/ http://chrony.tuxfamily.org/

CVSS Score

CVSSv2: 3.6 - AV:N/AC:H/Au:S/C:N/I:P/A:P
CVSSv3: 4.2 - CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L

Details

ntpd does not ensure that the key used to verify the authenticity of a packet actually belongs to the alleged sender of the packet.

The intended binding between keys (via keyids) and peers is indicated to ntpd through the configuration file. For example:

server ntp-server key 1

indicates that keyid 1 is a symmetric key shared with ntp-server. Packets bound for ntp-server should be authenticated under keyid 1 and, to prevent impersonation, packets from ntp-server should be authenticated using keyid 1.

Unfortunately, when receiving a packet, allegedly from ntp-sever, ntpd does not require the packet to authenticate under keyid 1. ntpd only ensures that the packet authenticates under some trustedkey known to ntpd. This allows any authenticated peer to impersonate any other authenticated peer.

We confirmed this vulnerability with the following setup. (The tests below were performed with NTP 4.2.8p3. 4.2.8p4 appears to introduce regressions which break LOCAL refclocks and symmetric associations.)

  • ntp-server - simulated stratum 1 server to provide time to clients

      # /etc/ntp.conf
      keys /etc/ntp.keys
      trustedkey 1
      server 127.127.1.1 prefer
      fudge 127.127.1.1 stratum 0
    
      # /etc/ntp.keys:
      1 MD5 youllneverguess
      2 MD5 you_really_wont
    
  • ntp-client - authenticates ntp-server with keyid 1 and ntp-client2 with keyid 2

      # /etc/ntp.conf
      keys /etc/ntp.keys
      trustedkey 1 2
      server ntp-server key 1 minpoll 2 maxpoll 2 iburst
      peer ntp-client2 key 2 minpoll 2 maxpoll 2 noselect
    
      # /etc/ntp.keys
      1 MD5 youllneverguess
      2 MD5 you_really_wont
    
  • ntp-client2 - authenticates ntp-server with keyid 1, uses keyid 1 to talk to ntp-client

      # /etc/ntp.conf
      keys /etc/ntp.keys
      trustedkey 1 2
      server ntp-server key 1 minpoll 2 maxpoll 2 iburst
      peer ntp-client key 1 minpoll 2 maxpoll 2
    
      # /etc/ntp.keys
      1 MD5 youllneverguess
      2 MD5 you_really_wont
    
  • attacker1 (192.168.33.9) - conducts a man-in-the-middle attack using ntp-client2’s peer key (keyid 2) to impersonate ntp-server to ntp-client and shift time

    attacker1 uses an ARP-spoofing attack to intercept and replay all packets between ntp-client and ntp-server. When it receives a server mode packet, it modifies the sent and recv time to be 100 years in the future. It then authenticates the packet with ntp-client2’s key (keyid 2). In this specific attack, the timestamps overflow so 100 years in the future from 2015 is 1979.

    ntp-client accepts the forged replies as though they were coming from ntp-server and, eventually, steps its clock.

Do The Sender and Recipient Have to Agree on the Keyid?

According to the ntpd documentation html/authentic.html:

The servers and clients involved must agree on the key ID, key type and key to authenticate NTP packets.

Though it doesn’t indicate precisely what “agreement” means or why, this conflicts with RFC 5905 which states:

keyid: Symmetric key ID for the 128-bit MD5 key used to generate and verify the MAC. The client and server or peer can use different values, but they must map to the same key.

This statement is problematic and only partially true. It is problematic because it does not address the binding of symmetric keys (specified by keyid) to the peers that they authenticate, allowing for impersonation between peers. It is only partially true because, in client-server modes, the server will use the keyid from the client mode packet to authenticate the incoming client mode packet as well as the outgoing server mode packet. Thus, in a benign scenario, clients and server must agree both on keyid and the key value. In symmetric modes, the statement is partially true because each peer uses the keyid specified in its peer association when generating an outgoing packet. However, in all cases, the keyid specified in an incoming packet will be used to authenticate that packet. Therefore, even in symmetric modes, if the two peers use different keyids A and B for a given symmetric association, each peer must map both keyids to the same key — the sender will use A (respectively B) to generate the authenticator for the outgoing packet and, therefore, the recipient will use A (respectively B) to authenticate the incoming packet. This is illustrated by the non-normative example code from RFC 5905:

  • According to receive() (https://tools.ietf.org/html/rfc5905#page-78) the keyid in the received packet is used to look up the key to authenticate the packet.

  • The keyid in the received packet is used to set the peer keyid for manycast peers (https://tools.ietf.org/html/rfc5905#page-78), symmetric peers (https://tools.ietf.org/html/rfc5905#page-79), and broadcast peers.

  • The keyid in the received packet is also used to choose the keyid and key to authenticate the transmitted packet in fast_xmit() (https://tools.ietf.org/html/rfc5905#page-88)

  • In peer_xmit() (https://tools.ietf.org/html/rfc5905#page-108) the keyid of the peer is used to authenticate the transmitted packet. It appears to omit setting the keyid on the transmitted packet. But, for preemptable associations, the peer keyid came from a received packet. For configured associations, it’s the value from the configuration file.

This is also borne out by the ntpd source code. When ntpd receives an incoming packet with an authenticator, it verifies the authentication under the key specified by the keyid from the incoming packet:

  • receive() reads the keyid from the packet:

      skeyid = ntohl(((u_int32 *)pkt)[authlen / 4]);
    
  • receive() then calls authdecrypt() with that keyid:

      if (!authdecrypt(skeyid, (u_int32 *)pkt, authlen,
          has_mac))
          is_authentic = AUTH_ERROR;
      else
          is_authentic = AUTH_OK;
    
  • authdecrypt() calls authhavekey() which looks up the key by keyid:

      if (id == sk->keyid) {
          if (0 == sk->type) {
    
  • And finally, MD5authdecrypt() is called to compute and verify the digest using the key value found via the packet’s key id

  • But, there is never a check which verifies that the keyid from the incoming packet (skeyid) matches the keyid configured for the peer association in question (peer->keyid)

Test Case: Equal keyids Refer to Differing Key Material

To verify that the sender and recipient can’t merely use different keyids to refer to the same key material unless both have been configured to map both keyids to the same key, we configured ntp-client2 with the same keys as ntp-server and ntp-client, but we swapped the keyids on ntp-client2. This confirmed that, though ntp-client2 was using the same key material as the other two nodes, all packets from ntp-client2 would fail authentication because the other nodes did not also have the same key material mapped to the keyids sent by ntp-client2.

  • ntp-client can sync with ntp-server just fine

      # /etc/ntp.keys
      1 MD5 youllneverguess
      2 MD5 you_really_wont
    
      # /etc/ntp.conf
      keys /etc/ntp.keys
      trustedkey 1 2
    
      server ntp-server key 1 minpoll 2 maxpoll 2 iburst
      peer ntp-client2  key 2 minpoll 2 maxpoll 2
    
      ntp-client$ ntpq -c as
    
      ind assid status  conf reach auth condition  last_event cnt
      ===========================================================
        1 43544  f65a   yes   yes   ok   sys.peer    sys_peer  5  # ntp-server
        2 43545  e01a   yes    no   ok     reject    sys_peer  1  # ntp-client2
    

    ntp-client detects that ntp-client2’s packets fail authentication. However, because ntp-client2 is also rejecting ntp-client’s packets due to bad auth, the origin timestamp doesn’t match and that check fails first before an auth check is performed. Note the auth 2 (AUTH_ERROR) on line 2 of the log snippet below.

      Nov  3 19:23:37 ntp-client ntpd: (ntp_proto.c): calling authdecrypt
      Nov  3 19:23:37 ntp-client ntpd: receive: at 698 192.168.33.11<-192.168.33.13 mode 1 keyid 00000001 len 68 auth 2
      Nov  3 19:23:37 ntp-client ntpd: (ntp_proto.c): big switch
      Nov  3 19:23:37 ntp-client ntpd: (ntp_proto.c): AM_PROCPKT, regular packet
      Nov  3 19:23:37 ntp-client ntpd: (ntp_proto.c): done with big switch
      Nov  3 19:23:37 ntp-client ntpd: (ntp_proto.c): flip is 0, not interleaved
      Nov  3 19:23:37 ntp-client ntpd: (ntp_proto.c): met bogus condition (p_org is not qual to peer->aorg), test2
    
  • ntp-client2 uses the same keys but different ids.

      # /etc/ntp.keys
      2 MD5 youllneverguess
      1 MD5 you_really_wont
    
      # /etc/ntp.conf
      keys /etc/ntp.keys
      trustedkey 1 2
    
      server ntp-server key 2 minpoll 2 maxpoll 2 iburst
      peer ntp-client   key 1 minpoll 2 maxpoll 2
    

    We can see that ntp-client2 is rejecting both the server and the peer due to bad auth.

      ntp-client2$ ntpq -c as
    
      ind assid status  conf reach auth condition  last_event cnt
      ===========================================================
        1 64340  c01c   yes    no   bad    reject              1  # ntp-server
        2 64341  c01c   yes    no   bad    reject              1  # ntp-client
    

    We can see this when ntp-client2 receives a packet from ntp-client:

      Nov  3 19:40:29 ntp-client2 ntpd: receive: at 1438 192.168.33.13<-192.168.33.11 mode 1 keyid 00000002 len 68 auth 2
      Nov  3 19:40:29 ntp-client2 ntpd: (ntp_proto.c): big switch
      Nov  3 19:40:29 ntp-client2 ntpd: (ntp_proto.c): AM_PROCPKT, regular packet
      Nov  3 19:40:29 ntp-client2 ntpd: (ntp_proto.c): done with big switch
      Nov  3 19:40:29 ntp-client2 ntpd: (ntp_proto.c): flip is 0, not interleaved
      Nov  3 19:40:29 ntp-client2 ntpd: (ntp_proto.c): digest failed
      Nov  3 19:40:29 ntp-client2 ntpd: event at 1438 192.168.33.11 c01c 8c bad_auth digest
    

Test Case: Using Different keyids That Map To The Same Key Material

This tests confirms that, as long as the sender and the recipient have the same key material configured for a given keyid, the recipient won’t enforce the binding between the keyid configured for an association and the keyid used to authenticate an incoming packet under that association.

We configure the same keys on both hosts

# /etc/ntp.keys on both hosts
1 MD5 youllneverguess
2 MD5 you_really_wont

But ntp-client uses keyid 2 to communicate with ntp-client2.

# ntp-client /etc/ntp.conf
keys /etc/ntp.keys
trustedkey 1 2

server ntp-server iburst key 1 minpoll 2 maxpoll 2
peer ntp-client2 key 2

And ntp-client2 uses keyid 1 when sending packets to ntp-client.

# ntp-client2 /etc/ntp.conf
keys /etc/ntp.keys
trustedkey 1 2

server ntp-server iburst key 1 minpoll 2 maxpoll 2
peer ntp-client key 1

As can be seen here, both ntp-client and ntp-client2 accept the packets from their peer despite being configured to use different keyids.

ntp-client$ ntpq -c as
ind assid status  conf reach auth condition  last_event cnt
===========================================================
  1 55125  f65a   yes   yes   ok   sys.peer    sys_peer  5  # ntp-server
  2 55126  f03a   yes   yes   ok     reject    sys_peer  3  # ntp-client2


ntp-client2$ ntpq -c as
ind assid status  conf reach auth condition  last_event cnt
===========================================================
  1 53439  f63a   yes   yes   ok   sys.peer    sys_peer  3  # ntp-server
  2 53440  f024   yes   yes   ok     reject   reachable  2  # ntp-client

Possible Fix

The following (untested) patch ensures that the keyid used to authenticate the packet is the same as the keyid configured for the corresponding peer association. If not, it sets is_authentic to AUTH_ERROR and skips the call to authdecrypt().

diff --git a/ntpd/ntp_proto.c b/ntpd/ntp_proto.c
index 2a15d72..01959f0 100644
--- a/ntpd/ntp_proto.c
+++ b/ntpd/ntp_proto.c
@@ -840,6 +840,11 @@ receive(
 		}
 #endif	/* AUTOKEY */

+ 		/* Ensure that the packet actually came from the expected peer */
+ 		if(skeyid <= NTP_MAXKEY && peer && skeyid != peer->keyid) {
+  			is_authentic = AUTH_ERROR;
+  			// TODO: log impersonation attempt
+
 		/*
 		 * Compute the cryptosum. Note a clogging attack may
 		 * succeed in bloating the key cache. If an autokey,
@@ -847,11 +852,13 @@ receive(
 		 * again. If the packet is authentic, it can mobilize an
 		 * association. Note that there is no key zero.
 		 */
-		if (!authdecrypt(skeyid, (u_int32 *)pkt, authlen,
+  		} else if (!authdecrypt(skeyid, (u_int32 *)pkt, authlen,
 		    has_mac))
 			is_authentic = AUTH_ERROR;
 		else
 			is_authentic = AUTH_OK;
+
+
 #ifdef AUTOKEY
 		if (crypto_flags && skeyid > NTP_MAXKEY)
 			authtrust(skeyid, 0);

Credit

Discovered by Matt Street of Cisco ASIG.

Timeline

2015-10-07 - Vendor Disclosure
2016-01-19 - Public Release