nav search
Data Center Software Security Transformation DevOps Business Personal Tech Science Emergent Tech Bootnotes BOFH

How the HTTPS-snooping, email addy and SSN-raiding HEIST JavaScript code works

No middleman required to ding sites for info

By Iain Thomson, 5 Aug 2016

Black Hat Malicious ads can potentially masquerade as people online and grab their personal information from HTTPS-protected websites, two boffins have shown.

The technique is dubbed HEIST – HTTP Encrypted Information can be Stolen through TCP-Windows – and it was devised by Tom Van Goethem and Mathy Vanhoef, both PhD researchers at the University of Leuven in Belgium.

The attack involves sneaking malicious JavaScript onto a webpage to swipe people's personal details, such as social security numbers and email addresses, from HTTPS-encrypted websites. The code can be smuggled into dodgy ads or simply placed on a website by hackers or an unscrupulous administrator.

It was demonstrated [slides, white paper] at Black Hat in Las Vegas this week.

An attacker doesn't have to intercept or snoop on the encrypted web traffic flowing over the network to decode it. Instead, the JS code pulls off a side-channel attack to extract the sensitive goodies.

When a visitor visits a webpage tainted with the HEIST script, the malicious code silently runs and fires off lots of requests to a particular website – perhaps a popular online bank or healthcare portal – that the victim is still logged into. The script runs code that looks like the following; in this example, it requests someone's profile page on a healthcare portal:

fetch('https://myhealthcare.org/myprofile',
{ mode: "no-cors", credentials: "include"});

This calls fetch(), telling it to hand over the user's credentials – their secret session cookie so we can continue to login as them – and to use a standard HTTP GET call to fetch the profile page. This last bit is important: for security reasons, when using HEAD, GET or POST, the JavaScript code is not allowed to see the encrypted page returned by the website. So far, so good.

fetch() is aimed at asynchronous communications: it returns a Promise object that triggers a callback as soon as the first byte is received from the server, even if it's an encrypted connection. Using performance.getEntries() it is possible to find out when the last byte was received. So we have a start time and a stop time.

Now we're cooking on gas. Now we know how long it took to receive the whole response. The packets of data will have been sent in batches, known as congestion windows, one window at a time, one after the other, with a slight pause at the end of each window for the client to acknowledge to the server that everything is going OK.

It is possible, judging from the time taken, to work out if a fetched page fitted inside one or more than one window: any delays will be apparent to the HEIST script. The JavaScript code is not allowed to know how much data is returned, in terms of the number of bytes – it has to rely on timing to judge how large a response is.

Now, most websites employ a compression algorithm to reduce the amount of data transferred. If there's a block of duplicated information on the page, the server sends that block once. If the HEIST script can send a request to a website knowing that part of the request will appear on the page, it can vary that request to, byte by byte, sniff out the contents of the encrypted page.

Let's say the page being targeted includes an email address, bob@example.com, and the request sent includes some text that will appear in a HTML form on the returned page – we're imagining some sort of settings page, here. We want to find out the email address. So first we send aaa@example.com and get back, say, 200 bytes of compressed encrypted data. We next send a combination of addresses until we hit bob@example.com and get back 184 bytes. The address in the HTML form matched the address we want, the compression algorithm replaced the duplicated data with a placeholder to the first instance of the matching text, and our script senses that as shorter data.

It's possible, in a matter of seconds, to replay this over and over again and slowly construct the information required from the page, by watching for when the returned data dropped from fitting in two TCP windows to one window. It may even be possible to lift sensitive information such as CSRF tokens from people's sessions.

Don't forget though, to work, HEIST requires a webpage that reflects part of the browser's request in its own content, and for the victim to have enabled JavaScript execution and third-party cookies.

Response

It's similar to the 2012 CRIME and 2013 BREACH attacks that exploited compression to decrypt web traffic.

"The impact of our findings has been illustrated by showing how compression-based attacks such as BREACH can be executed without requiring a man-in-the-middle position," Van Goethem and co-author Mathy Vanhoef said in their white paper.

"Additionally, by using search-based oracles, personal information such as credit card numbers can be obtained, and the medical conditions of victims can be exposed. Finally, we have argued that it is difficult to defend against our attacks."

You may think that the more modern HTTP/2 protocol might provide some protection against this attack, but the two said it actually makes things worse because it supports parallel requests, which speeds up the overall technique.

The researchers said that the only way to protect against HEIST is to disable third-party cookies, which is an option on most browsers but not enabled by default. Disabling third-party cookies would prevent HEIST's fetch() call from authenticating with the raided webpage.

The pair added that they had been in contact with some developers and patches are being worked on. ®

The Register - Independent news and views for the tech community. Part of Situation Publishing