How I discovered CCS Injection Vulnerability (CVE-2014-0224)

05 Jun 2014

Hello. My name is Masashi Kikuchi. Here is my story how I find the CCS Injection Vulnerability. (CVE-2014-0224)

What is the bug?

The problem is that OpenSSL accepts ChangeCipherSpec (CCS) inappropriately during a handshake. This bug has existed since the very first release of OpenSSL.

Message flow for a full handshake

In a correct handshake, the client and the server exchange messages in the order as depicted in this figure. (See RFC5246 The Transport Layer Security (TLS) Protocol Version 1.2 §7.3)。

ChangeCipherSpec MUST be sent at these positions in the handshake. OpenSSL sends CCS in exact timing itself. However, it accepts CCS at other timings when receiving. Attackers can exploit this behavior so that they can decrypt and/or modify data in the communication channel.

How difficult is it to find the bug?

The biggest reason why the bug hasn’t been found for over 16 years is that code reviews were insufficient, especially from experts who had experiences with TLS/SSL implementation. If the reviewers had enough experiences, they should have been verified OpenSSL code in the same way they do their own code. They could have detected the problem.

Fuzzing may have worked. However, as the history (see below) shows, knowledge of TLS/SSL implementation seems vital.


The first chance to find CCS Injection was in 2004, when Codenomicon found CVE-2004-0079. The following verification was added as a fix against this.

Fix null-pointer assignment in do_change_cipher_spec() revealed

This ensures that CCS is accepted only when new_cipher is set. Nevertheless, since new_cipher is already set in process of ServerHello, it cannot prevent CCS Injection.


The next chance was CVE-2009-1386.

DTLS: SegFault if ChangeCipherSpec is received before ClientHello

This was a problem that receiving CCS too early in DTLS handshake causes a segmentation fault. Again CCS timing verification hasn’t been fixed.

DTLS fragment retransmission bug

OpenSSL ticket #1950 also fixes the behavior over CCS.

DTLS fragment retransmission bug

This fix adds a check against unfortunate packet reorder on the wire. This solves the timing problem in DTLS handshake. As analyzed in the ticket, an uncomputed master secret is used for encryption. The value is said to be read from uninitialized memory and the random value causes a failure. But the truth is: the value was an empty byte sequence, i.e. not a random value. If this observation has been noted, possibility of the attack could have been alerted.

How difficult is it to implement CCS correctly?

It is easy to correctly implement CCS. Just send and receive messages in the order drawn in the protocol flow above. There is a little pitfall here, however, CCS uses a different type of record than other handshake messages. RFC explains the reason as follows:

  Note:          To help avoid pipeline stalls, ChangeCipherSpec is
                 an independent SSL Protocol content type, and is not
                 actually an SSL handshake message.
draft-ietf-tls-ssl-version3-00 §5.5

IMHO, this sentence is the very cause of the vulnerability. According to it, the reason that CCS is assigned an independent record type is to avoid a stall. This is the point where the most complex synchronization is required in TLS/SSL handshake. First, you need to wait until the handshake proceeds to the proper phase. Then, you need to check whether the handshake receives CCS before Finish.

More precisely, when accepting CCS, you must verify the following three conditions (*):

Being more careful, you should also check

as described in Alert attack.

I think the RFC can be clarified as:

Note: To help avoid pipeline stalls, ChangeCipherSpec is an independent SSL Protocol content type, and is not actually an SSL handshake message.

How did I discover the bug?

When Heartbleed arose, everyone talked about how to prevent similar bugs. Unit tests, code analyzers, fork and rewrite cleanly, improve API, not to reinvent malloc, use other language than C, etc. etc.

One of the favored candidate for such a language was ATS. I thought how it would be to write TLS/SSL in Coq, which I am more familiar with. Proving the protocol safety is a huge job, while it would not contribute to implementation safety. I try to create something that can show the correctness of the implementation at a glance.

My goals were:

I set the above conditions (*) by start thinking from transitions on CCS, as I know it is the most complicated part in the state machine. (Not overly complicated as though it’s the worst.)

Next I check whether existing implementations correctly verify these conditions. Most implementations except OpenSSL verify them, more or less. OpenSSL seems not doing at all. Later I confirmed that OpenSSL is actually exploitable.