Python - TLS Downgrade
Last updated
Was this helpful?
Last updated
Was this helpful?
Now that the app is running let's go hacking!
When visiting our browser warns us about a bad, self-signed certificate. In a real situation that would be a bad thing to encounter, but in our test lab it's to be expected. At least we have a safe HTTPS connection, right?
After confirming the exception, we will be greeted by the following web app. It has no functionality to play with, it's just a friendly page of text.
Let's take a look under the hood!
The OpenSSL suite has a command line component: openssl. This command allows you to perform many TLS/SSL activities such as inspecting and converting certificates. It also lets you test a TLS/SSL connection!
At first glance, things look okay! TLSv1.2 with ECDSA-RSA is a reasonable standard by any means!
A few things stand out:
As we already knew, this server uses a self-signed certificate.
The server uses the current TLS standard v1.2 with a strong set of algorightms.
The server tells us that "Secure Renegotiation IS supported".
That final bit, tells us that the server is willing to discuss with us any alternate standards and algorithms. Let's see if we can use older standards for TLS.
Alright, so we can drop down to TLSv1.1. Any further?
Current versions of the openssl command no longer have SSLv2 and v3 compatibility built in, unless you compile it from source. Luckily there are other testing tools at our disposal! Anyone with a Linux system can use on of the following:
First, let's take a look with SSLyze.
SSLyze tells us that, while SSLv2 is not offered, SSLv3 is in fact available. Legacy RC4 algorithms also appear to be supported, meaning we could get lucky in cracking the encyrption.
Let's see if TestSSL.sh tells us anything else.
Performing an actual attack would require that we set up a man-in-the-middle (MitM), to intercept and adjust the traffic. Luckily we have prepared just the thing for you!
You will need to open a shell in the running Docker container. This requires that you figure out its ID, using docker ps
. Just adding the first few characters of the ID with docker exec
is enough.
In your working directory /TLS-downgrade
you will find the web application's files as well as a few tools to peform the Man In The Middle attack.
The shell script start-interceptor.sh
is used to add a local firewall rule and to start the Python script (interceptor.py
) that interacts with this firewall rule.
If you start the interceptor without any modifications, it will pass on the TLS Client Hello unaltered. Doing so will show debugging output in your Bash session. TLSv1.0 and v1.1 connections don't trigger the MitM, while TLSv1.2 does.
The screenshot above shows the interceptor's debugging output for three consecutive OpenSSL tests.
The screenshot also shows two orange boxes around "\x03\x03". These are the bytes that indicate the TLS version. We will be chancing this to "\x03\x01" later on.
By default, your browser and the openssl
will try the strongest, most modern settings through TLSv1.2. Doing this manually with openssl
means you just leave off the forced version:
openssl s_client -connect 10.0.0.50:5000 </dev/null
You will remember that OpenSSL and modern-day browsers should protect you against TLS downgrade attacks. So it's good that our Docker container has an old version of OpenSSL. ;) To make sure that it works, while the interceptor is running, do the following from your host OS:
Go back to your Bash session in the Docker container and interupt/kill the running start-interceptor.sh
. You can do this by pressing .
Edit the interceptor.py script, with vi
. You need to be on line 87, where you need to change one byte from "\x03" to "\x01". Make the change, save the file and exit the text editor.
Now start the interceptor again using ./start-interceptor.sh
.
Then, perform the openssl s_client
command again through docker exec
.
The interception logs should show that the "\x03\x03" was changed into "\x03\x01":
And the openssl s_client
command should show that the TLS standard was downgraded by the MiTM, to TLSv1.0.
If you now visit the web application with your browser, it should warn you that it does not want to visit sites running TLSv1.0 or v1.1.
One important take-away is that modern browsers have been built in such a way as to protect you against attacks like these. But more importantly: don't take it at face-value that a site offering HTTPS will always offer you the best and strongest configuration. A man-in-the-middle can in some cases force both client and server to downgrade their algorithms to something more easily cracked. Your browser had no intentions of asking for TLSv1.0, but the Man In The Middle changed the setting along the way.
Let's delve into the code to see what's happening here. Like before, you can enter your Docker container using docker exec
.
The file TLS-downgrade.py
forms the entry point for this web application. Like most of the SKF Labs applications, it runs Flask with Python. The TLS listener is configured as follows:
It looks like someone has deliberately enable every cipher available to OpenSSL and has also re-enabled SSLv3. This doesn't have to be a malicious action, it could be down to inexperience or even a design decision based on the need to support legacy applications like IE6, IE8 or Java 6.
However, we can't go on like this! We have to fix things.
Next up, we really should not allow SSLv3 anymore, so we'll take out the manual override. And finally, if we would like to also disable the deprecated TLSv1.0 and v1.1, we could tell Python's SSL Context to only accept v1.2.
The new, resulting code would be:
Well now! That looks a lot simpler! And it even protects us against the MitM TLS-downgrade attack!
Oddly, the old OpenSSL in the Docker container still reports the protocol version as v1.0, but the Cipher set is set to NULL, meaning no session can be built. Running the same test from a modern Linux renders as follows:
If you'd really like to bring this application into the 21st century, then upgrades are in order! Replacing Pything 2.7 with anything >= 3.7 would be great. Ditto for OpenSSL, which should really be replaced with anything over 1.1.0. Making changes like those, would require a rebuild of the Docker container though, since you'd be looking at upgrading to Alpine 3.15 for example. ;)
Okay! We've gotten the server down to TLSv1.0, which by all means is deprecated.
Is there anything below TLSv1.0? Why sure there is! Back in the day we had SSLv2 and SSLv3 which are now famous for being . Using specialized tools and a little patience, POODLE will allow a malicious party to intercept and decrypt your traffic! This is why .
In the end, it looks like this web application was made a long time ago and was meant to serve end-users with old browsers. Think "Internet Explorer 6 old". Maybe it's from some corporate network; you never what you'll find in those! The site appears to be potentially vulnerable to the POODLE and and attempts on cracking RC4.
The firewall rule that we create will make sure every incoming TCP/IP packet is parsed by the Python script interceptor.py
. This script passes every packet onwards to the Docker container, except for TLSv1.2 "Client Hello" packets. This packet is the first step in setting up a working TLS connection, all of which is explained in grand details in .
First of all, all the ciphers need to go! We will have to trust the OpenSSL defaults to be stronger than what's configured now. that: "Changed in version 2.7.16: The context is created with secure default values. [...] The initial cipher suite list contains only HIGH ciphers, no NULL ciphers and no MD5 ciphers". That would suggest an upgrade might be needed.