TLS, Server Name Indication and Why We Need to Encrypt It



Yesterday I released version 1.6.1 of bettercap and among other things, you can read in the changelog:

* Huge improvement on HTTPS parser, now it parses TLS Client Hello messages with SNI extension in order to extract the real hostname.
...

But what does this actually mean? And how can we protect ourselves from it? (Hint: we can’t, yet)

sni

Server Name Indication

Let’s take a simple HTTP request to explain the concept, this is a GET request to the index of somesite.com:

GET / HTTP/1.1
Host: somesite.com
Connection: close

As we all know, once it gets this request the server is able to “understand” what virtual host it’s supposed to serve by reading the Host header, but what happens when the request is HTTPS and therefore the server can not read the Host header before providing the certificate?
What if server X is hosting multiple domains behind HTTPS? What’s the certificate it should send to the client? Here it comes Server Name Indication ( SNI ) for the rescue!

SNI is a mechanism which has been introduced in TLS as an extension to solve this problem, long story short, during the TLS handshake the client will send the name of the host it wants to connect to ( pretty much like the Host header on HTTP ), this piece of information is going to be transmitted unencrypted ( it has to! ), therefore we can intercept it.

As you can see in the image, by parsing such handshakes, bettercap is now able to tell you exactly what websites the target is browsing even if they’re on HTTPS, while the version before only “assumed” which was the domain by resolving it from the IP (and most of the times, failing miserably to give any useful result).

How

Very simple, with just a few lines of (bad) code of course!

1
2
3
4
5
6
7
8
9
10
11
12
13
...

# poor man's TLS Client Hello with SNI extension parser :P
if pkt.respond_to?(:tcp_dst) and \
pkt.payload[0] == "\x16" and \
pkt.payload[1] == "\x03" and \
pkt.payload =~ /\x00\x00.{4}\x00.{2}([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})\x00/
hostname = $1
if pkt.tcp_dst != 443
hostname += ":#{pkt.tcp_dst}"
end

...

What can we do about it?

encrypt all the things

Seriously, there’s not much we can do about it right now, which means even if you’re using HTTPS only, the domains you’re browsing are leaked on the network anyway … adios privacy! The only logical thing would be to encrypt the SNI payload as suggested in this document ( tnx to Fabio for the link! ), but I guess we’ll have to wait some time :)

One more thing

USE A VPN, ESPECIALLY ON PUBLIC NETWORKS FOR F°°°’S SAKE!!!