CVE-2020-13777 is a high severity issue in GnuTLS. In a nutshell, GnuTLS versions between 3.6.4 (released 2018-09-24) and 3.6.14 (2020-06-03) have a serious bug in their session resumption code, which lets attackers either completely decrypt observed traffic (for TLS versions up to 1.2), or perform a man-in-the-middle (MITM) attack on the server (for TLS version 1.3).
Establishing a new TLS session takes time and computing power. A TLS client has to do many things, including checking the server certificate using slow public-key cryptography. To reduce overhead on later connections, TLS uses session resumption. Session resumption re-uses the cryptographic parameters of earlier connections in a later connection and skips large parts of the handshake – including certificate validation.
TLS offers several ways to perform session resumption. The current standard way is to use session tickets which work similar to browser cookies: after setting up the cryptographic parameters for a connection, the server encrypts the negotiated parameter with a key only known to itself. The server sends this encrypted data to the client in a NewSessionTicket message.
When establishing a followup connection, the client sends the session ticket back to the server in its client hello message. The server checks if it can decrypt the ticket and if it is new enough. If the server accepts the ticket, it then uses the cryptographic parameters to re-establish the session. As mentioned above, this process sidesteps certificate verification.
Figure 1 below shows the exchanged protocol messages for two consecutive TLS 1.2 connections. Note that the first connection contains the session ticket in the NewSessionTicket extension. In the second connection, the client sends the session ticket back to the server – and the handshake is abbreviated, near instantly switching to the encrypted session.
Affected versions of GnuTLS incorrectly implement a key part of this process: the server does not set a key to encrypt its parameters. Instead, the parameters are encrypted with a key consisting of all zeroes.
This means that anyone can decrypt the information stored in the session ticket – which contains the cryptographic information needed to establish a crypto session.
If a client communicates with a server via TLS 1.3, this lets any server perform a MITM attack on the client: after the initial connection (which the MITM can just forward), the attacker can use the session ticket to resume the connection. No certificate checks will be performed and the attacker can impersonate any server.
If the client communicates with the server using TLS 1.2, this has an even worse effect: anyone who is able to observe the connection can decrypt all content. This also affects old sessions saved to pcaps.
The impact of this vulnerability is reduced by the fact that GnuTLS is not typically used on servers; it is more commonly used by client software like wget. While a bit unusual it can, however, be used by servers like Apache.
Investigating CVE-2020-13777 on the wire
After being made aware of this bug, we wanted to figure out if we can detect this vulnerability on the wire. So – to start, we spun up a vulnerable version of the server, connected to it, and made a pcap of the session (see figure 2 below). Looking at either the pcap or the output of OpenSSL when connecting to the server reveals that the session ticket looks a bit suspicious – it starts with 16 zero bytes:
To investigate this further, we take a look at the GnuTLS source code. After digging around for a while we find two interesting functions. _gnutls_get_session_ticket_decryption_key in lib/stek.c uses the first 16 bytes of the session ticket to look up the key. We use a debugger to verify that a zero key is used for the vulnerable versions of GnuTLS. We furthermore find the exact data format of the session ticket that GnuTLS uses in the unpack_ticket function of lib/ext/session_ticket.c. After looking up a few constants, we arrive at the following session ticket format:
Looking at this format we find that we can use two pieces of information to detect this vulnerability. First – the first 16 bytes of the session ticket have to be zero. Second – we can prevent false positives by making sure that the session ticket has the correct length. Both the key name and the initialization vector are 16 bytes each. They are followed by a 2 byte length field, then length bytes of encrypted data and then 20 bytes of message authentication code.
Writing a Zeek detector
Now that we have the data format, we can write a Zeek script that can detect this vulnerability. The main event we use for this is ssl_session_ticket_handshake, which is raised for the NewSessionTicket message and contains the raw data in the extension. Note: this event has a small peculiarity and includes two bytes of length information before the actual content of the extension; so everything is offset by two bytes
Using this information, we can write a short script to detect this vulnerability:
The script checks that the first 16 bytes of the session ticket (after the 2 bytes of length) are zero. It then checks that the ticket is at least 56 bytes long; this is 2 (prepended length field) + 16 (key length) + 16 (initialization vector) + 2 (length) + 20 (mac). It then uses the length field to check that the ticket has the correct length.
In examining the source code of the package, you will notice that it is straightforward and simple, consisting only of a bit of wrapping around the source code we presented here. This again shows that with Zeek, the biggest challenge often is to determine which bits of your network traffic you are interested in. Once you have identified the interesting parts of the network traffic, Zeek easily and quickly, with a few lines of code finds what you are looking for.