Tuesday, July 1, 2014

OpenSSL CCS Attack

As you might see from my posts frequency, last months have been pretty busy to me. My hacking team and I are working really hard and we are achieving incredibly results which makes me happy but really busy as well. OpenSSL CCS Attack (CVE-2014-0224) is almost one month old and not super interesting to be exploited so far, but since we got a great experience on that specific vulnerability I decided to "fix-it" on my memories in the following way.

CVE-2014-0224 bug exists since the very first OpenSSL release and this makes (at least to me) the whole story very fascinating. The issue basically happens because OpenSSL inappropriately accepts the ChangeCipherSpec (CCS) during a handshake. The following picture shows the correct way to implement a full protocol handshake.

The bug finds its own start if If a ChangeCipherSpec message is injected after the ServerHello but before the master secret has been generated  (ClientKeyExchange). At this point ssl3_do_change_cipher_spec generates the keys pair and the expected Finished hash for the handshake with an empty master secret (implementation bug). Moreover, the keys pair will be latched because further ChangeCipherSpec messages regenerate the expected Finished hash, but not new keys anymore. The following image shows the injection time frame.



The buggy code is the following one (the red numbers follow the above description):

int ssl3_do_change_cipher_spec(SSL *s)
 {
 int i;
 const char *sender;
 int slen;

 if (s->state & SSL_ST_ACCEPT)
  i=SSL3_CHANGE_CIPHER_SERVER_READ;
 else
  i=SSL3_CHANGE_CIPHER_CLIENT_READ;

 if (s->s3->tmp.key_block == NULL)1
  {
  if (s->session == NULL)
   {
   /* might happen if dtls1_read_bytes() calls this */
         SSLerr(SSL_F_SSL3_DO_CHANGE_CIPHER_SPEC,SSL_R_CCS_RECEIVED_EARLY);
   return (0);
   }

  s->session->cipher=s->s3->tmp.new_cipher;
  if (!s->method->ssl3_enc->setup_key_block(s)) return(0); 2
  }

 if (!s->method->ssl3_enc->change_cipher_state(s,i))
  return(0);

 /* we have to record the message digest at
  * this point so we can get it before we read
  * the finished message */
 if (s->state & SSL_ST_CONNECT)
  {
  sender=s->method->ssl3_enc->server_finished_label;
  slen=s->method->ssl3_enc->server_finished_label_len;
  }
 else
  {
  sender=s->method->ssl3_enc->client_finished_label;
  slen=s->method->ssl3_enc->client_finished_label_len;
  }

 i = s->method->ssl3_enc->final_finish_mac(s,
  sender,slen,s->s3->tmp.peer_finish_md); 3
 if (i == 0)
  {
  SSLerr(SSL_F_SSL3_DO_CHANGE_CIPHER_SPEC, ERR_R_INTERNAL_ERROR);
  return 0;
  }
 s->s3->tmp.peer_finish_md_len = i;

 return(1);
 }

Fortunately a patch is available here and a simple go tool to check the bug presence is here. For having more detailed infos, please visit (this post, and the original post).

No comments: