Synopsis: OpenSSH CBC cipher mode plain text disclosure vulnerability
NetBSD versions: 4.0.1, 4.0
Thanks to: Martin Albrecht, Kenny Paterson and Gaven Watson
Reported in NetBSD Security Advisory: NetBSD-SA2009-005

Index: cipher.c
===================================================================
RCS file: /cvsroot/src/crypto/dist/ssh/cipher.c,v
retrieving revision 1.19
diff -u -r1.19 cipher.c
--- cipher.c    28 Sep 2006 21:22:14 -0000      1.19
+++ cipher.c    29 Jun 2009 23:39:57 -0000
@@ -61,32 +61,33 @@
       u_int   block_size;
       u_int   key_len;
       u_int   discard_len;
+       u_int   cbc_mode;
       const EVP_CIPHER        *(*evptype)(void);
} 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 },
       { "[email protected]",
-                               SSH_CIPHER_SSH2, 16, 32, 0, EVP_aes_256_cbc },
-       { "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 },
+                               SSH_CIPHER_SSH2, 16, 32, 0, 1, EVP_aes_256_cbc },
+       { "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 },
#ifdef ACSS
-       { "[email protected]",   SSH_CIPHER_SSH2, 16, 5, 0, EVP_acss },
+       { "[email protected]",   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 }
};

#ifndef ACSS
@@ -114,6 +115,12 @@
}

u_int
+cipher_is_cbc(const Cipher *c)
+{
+       return (c->cbc_mode);
+}
+
+u_int
cipher_mask_ssh1(int client)
{
       u_int mask = 0;
Index: cipher.h
===================================================================
RCS file: /cvsroot/src/crypto/dist/ssh/cipher.h,v
retrieving revision 1.2
diff -u -r1.2 cipher.h
--- cipher.h    28 Sep 2006 21:22:14 -0000      1.2
+++ cipher.h    29 Jun 2009 23:39:57 -0000
@@ -82,6 +82,7 @@
void    cipher_set_key_string(CipherContext *, Cipher *, const char *, int);
u_int   cipher_blocksize(const Cipher *);
u_int   cipher_keylen(const Cipher *);
+u_int   cipher_is_cbc(const Cipher *);

u_int   cipher_get_number(const Cipher *);
void    cipher_get_keyiv(CipherContext *, u_char *, u_int);
Index: packet.c
===================================================================
RCS file: /cvsroot/src/crypto/dist/ssh/packet.c,v
retrieving revision 1.26
diff -u -r1.26 packet.c
--- packet.c    28 Sep 2006 21:22:14 -0000      1.26
+++ packet.c    29 Jun 2009 23:39:57 -0000
@@ -84,6 +84,8 @@
#define DBG(x)
#endif

+#define PACKET_MAX_SIZE (256 * 1024)
+
/*
 * This variable contains the file descriptors used for communicating with
 * the other side.  connection_in is used for reading; connection_out for
@@ -154,6 +156,10 @@
/* roundup current message to extra_pad bytes */
static u_char extra_pad = 0;

+/* XXX discard incoming data after MAC error */
+static u_int packet_discard = 0;
+static Mac *packet_discard_mac = NULL;
+
struct packet {
       TAILQ_ENTRY(packet) next;
       u_char type;
@@ -189,6 +195,36 @@
       }
}

+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. */

int
@@ -1060,6 +1096,9 @@
       Mac *mac   = NULL;
       Comp *comp = NULL;

+       if (packet_discard)
+               return SSH_MSG_NONE;
+
       if (newkeys[MODE_IN] != NULL) {
               enc  = &newkeys[MODE_IN]->enc;
               mac  = &newkeys[MODE_IN]->mac;
@@ -1081,11 +1120,15 @@
                   block_size);
               cp = buffer_ptr(&incoming_packet);
               packet_length = get_u32(cp);
-               if (packet_length < 1 + 4 || packet_length > 256 * 1024) {
+               if (packet_length < 1 + 4 || packet_length > PACKET_MAX_SIZE) {
#ifdef PACKET_DEBUG
                       buffer_dump(&incoming_packet);
#endif
-                       packet_disconnect("Bad packet length %u.", packet_length);
+                       logit("Bad packet length %-10u.",
+                           packet_length);
+                       packet_start_discard(enc, mac, packet_length,
+                           PACKET_MAX_SIZE);
+                       return SSH_MSG_NONE;
               }
               DBG(debug("input: packet len %u", packet_length+4));
               buffer_consume(&input, block_size);
@@ -1094,9 +1137,13 @@
       need = 4 + packet_length - block_size;
       DBG(debug("partial packet %d, need %d, maclen %d", block_size,
           need, maclen));
-       if (need % block_size != 0)
-               fatal("padding error: need %d block %d mod %d",
+       if (need % block_size != 0) {
+               logit("padding error: need %d block %d mod %d",
                   need, block_size, need % block_size);
+               packet_start_discard(enc, mac, packet_length,
+                   PACKET_MAX_SIZE - block_size);
+               return SSH_MSG_NONE;
+       }
       /*
        * check if the entire packet has been received and
        * decrypt into incoming_packet
@@ -1118,11 +1165,19 @@
               macbuf = mac_compute(mac, p_read.seqnr,
                   buffer_ptr(&incoming_packet),
                   buffer_len(&incoming_packet));
-               if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0)
-                       packet_disconnect("Corrupted MAC on input.");
+               if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0) {
+                       logit("Corrupted MAC on input.");
+                       if (need > PACKET_MAX_SIZE)
+                               fatal("internal error need %d", need);
+                       packet_start_discard(enc, mac, packet_length,
+                           PACKET_MAX_SIZE - need);
+                       return SSH_MSG_NONE;
+               }
+
               DBG(debug("MAC #%d ok", p_read.seqnr));
               buffer_consume(&input, mac->mac_len);
       }
+       /* XXX now it's safe to use fatal/packet_disconnect */
       if (seqnr_p != NULL)
               *seqnr_p = p_read.seqnr;
       if (++p_read.seqnr == 0)
@@ -1252,6 +1307,12 @@
void
packet_process_incoming(const char *buf, u_int len)
{
+       if (packet_discard) {
+               if (len >= packet_discard)
+                       packet_stop_discard();
+               packet_discard -= len;
+               return;
+       }
       buffer_append(&input, buf, len);
}