xorl %eax, %eax

NetBSD-SA2009-005: Plaintext Recovery Attack Against SSH

leave a comment »

This is not a new vulnerability, it has been around for quite some time. However it was not fixed in NetBSD until now. When SSH operates in CBC mode, up to 32 bits of plaintext can be recovered by a remote attacker. To fix this, the NetBSD project made the CBC ciphers look less favorable like that:

 } ciphers[] = {
-	{ "none",		SSH_CIPHER_NONE, 8, 0, 0, EVP_enc_null },
-	{ "des",		SSH_CIPHER_DES, 8, 8, 0, EVP_des_cbc },
-	{ "3des",		SSH_CIPHER_3DES, 8, 16, 0, evp_ssh1_3des },
-	{ "blowfish",		SSH_CIPHER_BLOWFISH, 8, 32, 0, evp_ssh1_bf },
-
-	{ "3des-cbc",		SSH_CIPHER_SSH2, 8, 24, 0, EVP_des_ede3_cbc },
-	{ "blowfish-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, EVP_bf_cbc },
-	{ "cast128-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, EVP_cast5_cbc },
-	{ "arcfour",		SSH_CIPHER_SSH2, 8, 16, 0, EVP_rc4 },
-	{ "arcfour128",		SSH_CIPHER_SSH2, 8, 16, 1536, EVP_rc4 },
-	{ "arcfour256",		SSH_CIPHER_SSH2, 8, 32, 1536, EVP_rc4 },
-	{ "aes128-cbc",		SSH_CIPHER_SSH2, 16, 16, 0, EVP_aes_128_cbc },
-	{ "aes192-cbc",		SSH_CIPHER_SSH2, 16, 24, 0, EVP_aes_192_cbc },
-	{ "aes256-cbc",		SSH_CIPHER_SSH2, 16, 32, 0, EVP_aes_256_cbc },
+	{ "none",		SSH_CIPHER_NONE, 8, 0, 0, 0, EVP_enc_null },
+	{ "des",		SSH_CIPHER_DES, 8, 8, 0, 1, EVP_des_cbc },
+	{ "3des",		SSH_CIPHER_3DES, 8, 16, 0, 1, evp_ssh1_3des },
+	{ "blowfish",		SSH_CIPHER_BLOWFISH, 8, 32, 0, 0, evp_ssh1_bf },
+
+	{ "3des-cbc",		SSH_CIPHER_SSH2, 8, 24, 0, 1, EVP_des_ede3_cbc },
+	{ "blowfish-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, 1, EVP_bf_cbc },
+	{ "cast128-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, 1, EVP_cast5_cbc },
+	{ "arcfour",		SSH_CIPHER_SSH2, 8, 16, 0, 0, EVP_rc4 },
+	{ "arcfour128",		SSH_CIPHER_SSH2, 8, 16, 1536, 0, EVP_rc4 },
+	{ "arcfour256",		SSH_CIPHER_SSH2, 8, 32, 1536, 0, EVP_rc4 },
+	{ "aes128-cbc",		SSH_CIPHER_SSH2, 16, 16, 0, 1, EVP_aes_128_cbc },
+	{ "aes192-cbc",		SSH_CIPHER_SSH2, 16, 24, 0, 1, EVP_aes_192_cbc },
+	{ "aes256-cbc",		SSH_CIPHER_SSH2, 16, 32, 0, 1, EVP_aes_256_cbc },
 	{ "rijndael-cbc@lysator.liu.se",
-				SSH_CIPHER_SSH2, 16, 32, 0, EVP_aes_256_cbc },
+				SSH_CIPHER_SSH2, 16, 32, 0, 1, EVP_aes_256_cbc },
 #ifdef AES_CTR_MT
-	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, evp_aes_ctr_mt },
-	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, evp_aes_ctr_mt },
-	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, evp_aes_ctr_mt },
+	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, 0, evp_aes_ctr_mt },
+	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, 0, evp_aes_ctr_mt },
+	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, 0, evp_aes_ctr_mt },
 #else
-	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, evp_aes_128_ctr },
-	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, evp_aes_128_ctr },
-	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, evp_aes_128_ctr },
+	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, 0, evp_aes_128_ctr },
+	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, 0, evp_aes_128_ctr },
+	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, 0, evp_aes_128_ctr },
 #endif
 #ifdef ACSS
-	{ "acss@openssh.org",	SSH_CIPHER_SSH2, 16, 5, 0, EVP_acss },
+	{ "acss@openssh.org",	SSH_CIPHER_SSH2, 16, 5, 0, 0, EVP_acss },
 #endif
 
-	{ NULL,			SSH_CIPHER_INVALID, 0, 0, 0, NULL }
+	{ NULL,			SSH_CIPHER_INVALID, 0, 0, 0, 0, NULL }
 };

And they also wrote a couple of new functions to ignore packets up to 256KB since those affect specifically ciphers that utilize the CBC mode. To do this, the following constants were defined:

+#define PACKET_MAX_SIZE (256 * 1024)
+
    ...
+/* XXX discard incoming data after MAC error */
+static u_int packet_discard = 0;
+static Mac *packet_discard_mac = NULL;
+

And two new functions responsible for starting or stopping the discard of a packet were written. Those functions are:

+static void
+packet_stop_discard(void)
+{
+	if (packet_discard_mac) {
+		char buf[1024];
+		
+		memset(buf, 'a', sizeof(buf));
+		while (buffer_len(&incoming_packet) < PACKET_MAX_SIZE)
+			buffer_append(&incoming_packet, buf, sizeof(buf));
+		(void) mac_compute(packet_discard_mac,
+		    p_read.seqnr,
+		    buffer_ptr(&incoming_packet),
+		    PACKET_MAX_SIZE);
+	}
+	logit("Finished discarding for %.200s", get_remote_ipaddr());
+	cleanup_exit(255);
+}
+
+static void
+packet_start_discard(Enc *enc, Mac *mac, u_int packet_length, u_int discard)
+{
+	if (!cipher_is_cbc(enc->cipher))
+		packet_disconnect("Packet corrupt");
+	if (packet_length != PACKET_MAX_SIZE && mac && mac->enabled)
+		packet_discard_mac = mac;
+	if (buffer_len(&input) >= discard)
+		packet_stop_discard();
+	packet_discard = discard - buffer_len(&input);
+}
+
 /* Returns 1 if remote host is connected via socket, 0 if not. */

Finally, those two functions are used in a few places during the processing of the packets to discard specific ones. For example, packet_process_incoming() was patched like this:

 void
 packet_process_incoming(const char *buf, u_int len)
 {
+	if (packet_discard) {
+		keep_alive_timeouts = 0; /* ?? */
+		if (len >= packet_discard)
+			packet_stop_discard();
+		packet_discard -= len;
+		return;
+	}
 	buffer_append(&input, buf, len);
 }

Written by xorl

July 8, 2009 at 00:47

Posted in bugs, netbsd

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s