Talos Vulnerability Report

TALOS-2015-0065

Network Time Protocol Password Length Memory Corruption Vulnerability

Oct 21, 2015
Description

A potential buffer overflow vulnerability exists in the password management functionality of ntp. A specially crafted key file could cause a buffer overflow potentially resulting in memory being modified. An attacker could provide a malicious password to trigger this vulnerability.

Tested Versions

ntp 4.2.8p2

Product URLs

http://www.ntp.org

Details

The function MD5auth_setkey() is called in three places: - line 179 in authreadkeys.c - line 202 in authreadkeys.c - line 32 in authusekey.c

void MD5auth_setkey(keyid_t keyno, int keytype, const u_char *key, size_t len)

The function takes 4 arguments, the keyno, keytype, key and len. If a keyno doesn’t exist, then a new key will be allocated based on len. If keyno does exist, the new key will be copied over the old key, using the new length as a size for the strlcpy or memcpy function. The 2 calls in authreadkeys.c have size checks (20 and 32 bytes respectively):

178		if (len <= 20) {	/* Bug 2537 */
				MD5auth_setkey(keyno, keytype, (u_char *)token, len);
			} else {
				char	hex[] = "0123456789abcdef";
				u_char	temp;
				char	*ptr;
				size_t	jlim;

	186			jlim = min(len, 2 * sizeof(keystr));
				…
	202			MD5auth_setkey(keyno, keytype, keystr, jlim / 2);

In the code above, keystr is a 32 byte unsigned character array.

The one in authusekey does not have size checks:

28	len = strlen((const char *)str);
	if (0 == len)
		return 0;

32	MD5auth_setkey(keyno, keytype, str, len);

However it is called twice from the functions sendrequest and passwd in ntpd.c (lines 895 and 1785) and ntpq.c (lines 1217 and 2453). The code to call authusekey is identical in both files. Both functions retrieve the key via a call to getpass which requires console access and only use it for the info_auth_keyid. Below is the code from ntpd.c for the functions sendrequest and passwd respectively:

889	if (!authistrusted(info_auth_keyid)) {
			pass = getpass_keytype(info_auth_keytype);
			if ('\0' == pass[0]) {
				fprintf(stderr, "Invalid password\n");
				return 1;
			}
			authusekey(info_auth_keyid, info_auth_keytype,
				   (u_char *)pass);
			authtrust(info_auth_keyid, 1);
	898	}

	1776	if (pcmd->nargs >= 1)
			pass = pcmd->argval[0].string;
		else {
			pass = getpass_keytype(info_auth_keytype);
			if ('\0' == *pass) {
				fprintf(fp, "Password unchanged\n");
				return;
			}
		}
		authusekey(info_auth_keyid, info_auth_keytype, (u_char *)pass);
	1786	authtrust(info_auth_keyid, 1);

Below is the vulnerable code in the MD5auth_setkey function:

530	/*
		 * See if we already have the key.  If so just stick in the
		 * new value.
		 */
		bucket = &key_hash[KEYHASH(keyno)];
		for (sk = *bucket; sk != NULL; sk = sk->hlink) {
			if (keyno == sk->keyid) {
				sk->type = (u_short)keytype;
				secretsize = len;
				sk->secretsize = (u_short)secretsize;
	#ifndef DISABLE_BUG1243_FIX
				memcpy(sk->secret, key, secretsize);
	#else
				strlcpy((char *)sk->secret, (const char *)key,
					secretsize);
	#endif

If a key is set to 20 bytes and later replaced by a key that is 32 bytes or larger, no extra size checks will be performed, the key will simply be copied over the old key, potentially resulting in a heap-based buffer overflow. This allows an attacker to first provide a shorter key and then a longer one. This can be exploited either through the console using the getpass functionality or via the remote configuration facility: specifying a password file where a specific key is set to a short password and then replacing it with one up to 32 bytes in length, causing a buffer overflow.

Credit

Discovered by Yves Younan and Aleksander Nikolich of Cisco Talos.