HEIST – Attacks on the TLS Record Protocol
21.5.7 HEIST
In 2016, Mathy Vanhoef and Tom Van Goethem, two security researchers from Belgium, published yet another attack on TLS that exploits the compression side channel. They called their attack HTTP Encrypted Information can be Stolen through TCP-windows (HEIST) [175].
HEIST exploits a side channel leaking the exact size of any cross-origin response. Because this side channel is on the TCP level, HEIST allows us to mount CRIME and BREACH attacks purely in the web browser. With HEIST, Eve does not need man-in-the-middle access to Bob’s network traffic, and so she can mount CRIME and BREACH attacks remotely.
To measure the response times, HEIST uses a JavaScript concept called Promise. A Promise is a JavaScript object representing the eventual completion or failure of an asynchronous operation such as an HTTP request to a server. One such example is a GET request to retrieve a web page. A Promise object is always in one of three states:
- Pending: The initial state where the Promise is neither fulfilled nor rejected
- Fulfilled: A state that the Promise enters upon a successful completion of the asynchronous operation
- Rejected: A state that the Promise enters if the asynchronous operation fails
When a Promise is created, JavaScript immediately returns a Promise object. But the Promise itself is either eventually fulfilled or rejected based on the outcome of the fetch() process. Typically, the then() method is used to specify what code shall be executed in case the Promise is fulfilled or rejected.
Vanhoef and Van Goethem observed that the resolution of a Promise happens as soon as the first byte of the corresponding response is received and recognized the security implications of this behavior. Once the client receives the first byte of the response, the JavaScript code is notified by resolving the Promise and can start processing the response while it is still streaming in. If the response is larger than TCP’s maximum segment size, it will be split by the server into multiple segments and transmitted according to the congestion window size. After each such transmission, the server waits for a TCP acknowledgment message, ACK, from the client.
Thus, the resolution of a Promise coincides with the receipt of the initial congestion window. Consequently, if Eve can determine when the requested resource – for example, a web page – was completely downloaded, she can determine whether the response fits into a single congestion window. It turns out that Eve can accomplish this using JavaScript’s Resource Timing API. This API provides methods that return the time when a request was initiated and when it was completed. Listing 21.2 shows how the performance.getEntries() method can be used to obtain the times of a request and determine when the response was completely fetched using the responseEnd attribute:
Listing 21.2: Example JavaScript code for obtaining timing information
fetch(’https://example.com/foo’).then(
function(response) {
// first byte of ‘response‘ received!
T1 = performance.now();
}
);
setInterval(function() {
var entries = performance.getEntries();
var lastEntry = entries[entries.length – 1];
if (lastEntry.name == ’https://example.com/foo’) {
T2minT1 = lastEntry.responseEnd – T1;
}
}, 1)
Using the example code in Listing 21.2, Eve can send an HTTP GET request to fetch https://example.com/foo, determine the point in time when the first response byte arrives by executing T1 = performance.now(), and determine the point in time when the response was completely received by calling lastEntry.responseEnd. Finally, by computing the time difference T2minT1 = lastEntry.responseEnd – T1, Eve is able to find out whether the response fits into a single congestion window or not.
Moreover, Vanhoef and Van Goethem demonstrated that the exact size of the response can be calculated based on the timing information when a parameter of the HTTP request is reflected in the corresponding HTTP response.
Thus, to determine the secret value, Eve can simply repeatedly guess the values of the reflected parameter until she finds the largest possible guess for which the HTTP response still fits into the congestion window.
21.6 Summary
In this chapter, we covered attacks on the TLS Record protocol. Although we discussed the technical aspects of all these attacks, there were also some general lessons presented in this chapter.
Lucky 13 is a very educational example of how a seemingly innocuous theoretical weakness that is being ignored can become a critical security vulnerability over time. While Lucky 13 was introduced in 2013, the first practical padding oracle attack on CBC had already been described in 2002 by the French cryptographer Serge Vaudeney [176]. Yet it took over 10 years and the publication of Lucky 13 by AlFardan and Paterson for this type of attack to be taken seriously.
The BEAST attack illustrates that cryptographic notions such as IND-CPA that, on the face of it, are very theoretical, do have their value in practical, real-world security.
The POODLE attack is a good example of security risks posed by insecure legacy systems. In theory, POODLE is an attack on SSL 3.0 and, because at the time of its publication the underlying weakness was already fixed in TLS 1.2, should not have caused any problems. In practice, POODLE remained a threat to TLS because of the downgrade dance and the typically slow pace of upgrading legacy systems in the field.
Finally, the attacks exploiting the compression side channel demonstrate how the interaction of seemingly unrelated functions – lossless data compression of the plaintext and encryption – can lead to unanticipated, emerging properties such as a timing side channel.
In the next chapter, we will look into attacks that exploit implementation bugs in software stacks that implement TLS.