WhatImprovements to Nmap's ssl-enum-ciphers NSE script.
WhoMe! Original script by Mak Kolybabi and Gabriel Lawrence. Workaround for Microsoft servers discovered by Martyn Tovey.
WhySecure Sockets Layer and Transport Layer Security (hereinafter called SSL) are protocols for using various hashing and encryption algorithms to provide a secure, mutually authenticated channel for communication between computer systems. By design, the particular algorithms used are pluggable; the client and the server decide between themselves which ones to use for any particular communication. But not all algorithms are created equal, and some are deliberately neutered for testing purposes, or to avoid export restrictions on cryptography.
If a client and a server both support some weak encryption algorithm, they may still never use it if they both favor a strong one they also share. Unfortunately, the decision of which algorithm to choose is neither encrypted nor authenticated, so an attacker in a man-in-the-middle scenario can perform a downgrade attack, reducing security to the least-strong combination of algorithms, to even include unauthenticated or do-nothing encryption. Server admins, security teams, and penetration testers need a way to determine which servers on a network may be vulnerable to this type of attack.
Enter Nmap. Since version 5.30BETA1 (2010-03-29), Nmap has included a script for enumerating the algorithms for each of the 4 modern versions of SSL (SSLv3, TLSv1.0, TLSv1.1, and TLSv1.2). The mechanism this script uses (also used by sslscan and other tools) is simple: for each of the 4 SSL versions, try each of the 213 cipher suites (combinations of hashing algorithm, encryption algorithm, key exchange algorithm, and cipher mode) and report back which ones succeeded. That's 4*213=852 exchanges between client and server, no matter how many SSL versions or cipher suites each uses. A run against 30 HTTPS servers on the Internet took me 9.5 minutes. That's slow, especially considering each bunch of 213 cipher suites is tested in parallel. I was sure there had to be a better way.
HowIn the SSL protocol, the first message sent is from the client, the Client Hello message. It contains a list of cipher suites supported, as well as other information. The Server Hello message sent in reply contains one cipher suite (usually the strongest) that the server shares with the client. The protocol spec allows up to 2^16-1 cipher suites to be sent in the one Client Hello message.
Removing the multithreading code temporarily, I changed the script to follow this algorithm:
- Send a Client Hello with all the cipher suites we wish to enumerate.
- When the server responds with one, end the connection.
- Remove and record the chosen suite, and send a new Client Hello with the remaining cipher suites.
- Repeat until the server sends a handshake failure message, indicating that it supports none of the remaining suites.
This worked great! Instead of 213 exchanges, only 1 plus the number of valid cipher suites were performed. Usually this number is between 3 and 16 (most commonly 6, 7, or 8). Moving the threading code to run each protocol in parallel, scan times reduced by two-thirds or more. I cleaned up the code and sent it in to the development mailing list for comment.
The idea is not new: the original author of the script, Mak Kolybabi, implemented a very similar idea before the final version was added, but scrapped it due to inconsistent results. Unfortunately, comments from the mailing list revealed that my version of the script was faring no better. Specifically, Windows systems running IIS tended to show only 1 or 2 ciphers supported, when previously they had shown 6 or more. Examining the traffic, I found that the server was terminating the underlying TCP connection with a RST, with no handshake failure to show what might be wrong.
At this point, Martyn sent me a message with the key to the problem: contrary to the protocol spec, some servers only look at the first 64 cipher suites, rejecting or ignoring any higher number. Rushing back to the script, I quickly broke up the scan into 64-suite chunks. Success! This added an extra 3 exchanges per valid protocol, but that's a small price to pay for the overall speedup.