'Fun' with TLS - Automating the Testing of TLS connections

17 September 2021
|
6 min Read
|
Lauren Palmer
Unsplashed - Ilya Pavlov

Programmatically determining the version of the TLS protocol and cipher suites a server supports was not as easy as I expected it to be. TLS provides the Security layer in HTTPS, and secures the connection between you and the server when visiting websites. Previous versions of TLS are now deprecated and have known vulnerabilities. Checking a server isn’t running these outdated versions is a very important step in checking a website is secure. These are the steps I took to get to a working script.

Introduction

Transport Layer Security (TLS) is the successor to Secure Sockets Layer (SSL) and is a cryptographic protocol most notably providing the ‘S’ for Security layer in HTTPS. There are different versions of TLS and SSL, and some serious vulnerabilities against previous versions are available to exploit. When evaluating a website, it is important to check that it supports the latest TLS version to ensure it is providing a secure connection, but it is more important to check that the web server doesn’t support old vulnerable and deprecated versions that an attacker could leverage.

I wanted to check what versions of TLS and the cipher suites a website’s server supports without direct access to the configuration of the server. While it is possible to just check the configuration on the server, it is useful to check what is actually being publicly served. This is related to the next product Red Maple Technologies is working on, which will help manage the attack surface of a company’s publicly available digital assets, more information coming soon. 😉

Qualys SSL Labs and Mozilla Observatory have very helpful tools that will find TLS certificates on a specified domain and rate the certificate. It will also tell you the protocol versions the server supports, and the specific cipher suites. The rating is based on the configuration on the certificate and the protocol versions and cipher suites that are supported, i.e. supporting the deprecated and vulnerable SSLv2 will result in a bad rating.

Have you ever wondered how you could do this yourself? It couldn’t be that hard? Or so I thought… 😅

First, how does TLS work.

To start a session that uses TLS encryption there is a handshake, this happens automatically when a user visits a website over HTTPS. The handshake allows the client (the browser) and the server to select the version of TLS and the specific cipher suite to use, then it will authenticate the server’s identity using the certificate and generate keys to be used in the session. The TLS protocol defines how to provide secure communication between the client and server, the cipher suite defines the actual encryption algorithm used to encrypt the application data and there are quite a few to pick from.

During the handshake the client will send a list of the supported versions of TLS and cipher suites, the server will then pick the most desirable one from that list. A key point to remember for the rest of this article is you cannot simply ask the server what it supports you must give it options to choose from.

For more information on how the TLS handshake works, see this good reference by Cloudflare.

On to implementing…

How does a person go about determining what versions of TLS and cipher suites a server supports? Well, you must iterate through all possible cipher and protocol version combinations, and determine which ones return a successful connection. But first you have to find a suitable tool.

The web tools I mentioned previously are valid, but they didn’t fit my use case. I didn’t want to rely on an external API, that might not always be available. I also wanted to easily integrate the tool into the project I was working on. So I set out to find a tool I could run locally.

That doesn’t sound too bad.

Attempt 1: The Node TLS Library

The Node TLS library will return all the ciphers a service supports though it doesn’t separate these by protocol. When you make a connection, you can specify the cipher you want to use but it doesn’t let you limit the protocol. I also struggled to figure out how to turn on support for SSLv2 and SSLv3 and get the related ciphers for those. No good.

Attempt 2: Nmap

Then, I went to trusted stack overflow and found a helpful post. The two top answers linked to a custom script using OpenSSL and a Nmap script. Problem solved?

First, I tried the Nmap script. It worked great. Until I realised Nmap doesn’t currently support TLSv1.3. Agh!

Attempt 3: OpenSSL

Next the script. Did it run on the first go? Nope! 😞

I was running MacOS Big Sur, and the provided OpenSSL binary uses the LibreSSL 2.8.3 library. This doesn’t support TLSv1.3 either and SSL wasn’t enabled.

Side note, MacOS does support TLSv1.3 and it is used in their browser Safari but the LibreSSL 2.8.3 library don’t support the OpenSSL API calls to support TLSv1.3 according to this issue.

Attempt 4: Install OpenSSL from Source

I still wanted to use the script, so I just needed to install OpenSSL from source. Ideally, I wanted OpenSSL to support all the protocols (SSL and TLS), so that I could identify servers supporting vulnerable protocol versions and cipher suites.

My next search brought me to a script with the correct configuration for OpenSSL to turn on SSLv2 and SSLv3. The script notes that the latest version of OpenSSL v1.1.1 no longer supports SSLv2 at all, so it recommended using OpenSSL v1.0.2.

So, I modified the script to run on Mac instead of Ubuntu, and it failed to build. The build files had decided I was on a 32-bit system, not the 64-bit system I was on. Easy fix, I forced it into 64-bit mode and kicked off the build. But it fails! 😞 After some searching, I found out that building for MacOS is supported, but not MacOSX. And I would have to patch the makefiles (the files used to build OpenSSL from the source code). Ew! 😷

Attempt 5: Install OpenSSL v1.1.1 from Source

OpenSSL v1.1.1 does have SSLv3 and TLSv1.3. AND IT INSTALLED!!! 😀 At this point I am willing to forgo SSLv2.

Back to the original problem, getting the supported protocols and cipher suites from a server.

I tried running the script from the stack overflow article above, and it worked!! Though the output was a bit messy.

Attempt 6: Writing my own script

I decided to just run the necessary OpenSSL commands and extract the output how I wanted it formatted.

The basics of the script:

  1. For each protocol (TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3):

  2. Get the supported ciphers for that protocol

    openssl ciphers -${protocol} -s 'ALL:eNULL’
    
  3. For each cipher, try creating a connection with the desired host

    openssl s_client -${protcol} –(cipher||ciphersuites) '${cipher}' ${host} </dev/null 2>&1
    
  4. Check it was successful by testing the output doesn’t contain the string ‘Command failed’

  5. Add successful protocol cipher combinations to a list

And voila! You have a list of the protocols and cipher suites a server supports. Wasn’t that easy. 🎊

You can find more information on the commands I run: openssl ciphers and openssl s_client. I implemented this solution in JavaScript and that can be found here if you want to run it yourself.

To conclude…

It makes sense that mainstream libraries are dropping support for deprecated protocol versions, encouraging browsers and servers to use the latest versions. Unfortunately, that doesn’t help when you want to test if a server is still using a deprecated version.

When I started this task, it sounded simple enough. Though I have learned a lot about TLS. 😄

Related Blogs
About Lauren Palmer
Lauren is a Software Engineer with 5 years of experience problem solving and developing solutions for customers. Her skill set ranges from digital signal processing to web development. At Red Maple Lauren has been focused on the development of products to protect companies from cyber threats.