CVSS2
Attack Vector
NETWORK
Attack Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:M/Au:N/C:P/I:P/A:P
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
CHANGED
Confidentiality Impact
NONE
Integrity Impact
HIGH
Availability Impact
NONE
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N
EPSS
Percentile
86.8%
CERT VU#357792
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.
NTP 4.2.8p3
NTPsec a5fb34b9cc89b92a8fef2f459004865c93bb7f92
chrony 2.2
(http://www.ntp.org/)[http://www.ntp.org/] (https://www.ntpsec.org/)[https://www.ntpsec.org/] (http://chrony.tuxfamily.org/)[http://chrony.tuxfamily.org/]
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
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
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
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
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.
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)
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
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
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
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
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
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);
2015-10-07 - Vendor Disclosure
2016-01-19 - Public Release
CVSS2
Attack Vector
NETWORK
Attack Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:M/Au:N/C:P/I:P/A:P
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
CHANGED
Confidentiality Impact
NONE
Integrity Impact
HIGH
Availability Impact
NONE
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N
EPSS
Percentile
86.8%