When evaluating a remote target, learning more about the Secure Sockets Layer (SSL)/Transport Layer Security (TLS) configuration can be very useful. Being able to test SSL/TLS configurations on target machines is a common requirement when performing security assessments. As such, it is important to be able to perform these tests as independent of system configuration as possible. Today, most popular Linux distributions come bundled with the openssl package, which is the only tool that is required for these purposes [1].
The openssl package has the ability to attempt a connection to a server using the s_client command. What follows is a Linux bash script [2]. The following six line script will test a given port on a given server for supported versions of TLS, as well as supported ciphers.
for v in ssl2 ssl3 tls1 tls1_1 tls1_2; do for c in $(openssl ciphers 'ALL:eNULL' | tr ':' ' '); do openssl s_client -connect <server>:<port> \ -cipher $c -$v < /dev/null > /dev/null 2>&1 && echo -e "$v:\t$c" done done
Let's step through this code line by line.
for v in ssl2 ssl3 tls1 tls1_1 tls1_2; do
This line is a simple bash for loop structure. As it is the first of two loops, this will be referred to as the outer for loop. This loop will iterate five times, once each for the strings 'ssl2', 'ssl3', 'tls1', 'tls1_1', and 'tls1_2', changing the value of v each time. This covers each of the current major releases of SSL/TLS. The do at the end is simply proper syntax for bash for loops.
for c in $(openssl ciphers 'ALL:eNULL' | tr ':' ' '); do
This line is a bit more complicated. Again, this is a bash for loop structure. As this for loop is contained within the outer for loop, this will be referred to as the inner for loop. This loop will run once for each SSL/TLS version.
It is best to understand this one from the inside out. In bash, when a string is surrounded by $(), the shell will execute it as though it were a command. The resulting output then replaces the command itself when interpreting the rest of the line. In this case, the output of openssl ciphers 'ALL:eNULL' | tr ':' ' ' will be used in place of the command itself. This command, which we will call the combined command, is actually a combination of two smaller commands. The first command is openssl ciphers 'ALL:eNULL and the second command is tr ':' ' '.
The first command will output a colon-delimited list of all ciphers supported by the openssl package. An example of this output may look like this: ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384.
The pipe character (|) is an example of bash output redirection. The pipe tells the shell to use the output from the preceding command as the input for the following command. In this case, the colon-delimited list of supported ciphers (the output from the first command) will be used as input for the second command.
The tr command is short for translate. It can be used to quickly find and replace parts of strings. In our case, we want to replace every colon with a space to turn our comma-delimited list into a space-delimited list. This is done simply because bash for loops accept whitespace as a delimiter. The result of this using the previous example output would be: ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES256-GCM-SHA384.
What we end up with for this line is a loop that runs one time for each supported cipher, changing the value of c each time. Again, do is simply proper syntax for bash for loops.
The third and fourth lines are actually one line split for readability. The backslash is simply a way to inform the shell that the command will continue on the next line. As such, we will review them as one.
openssl s_client -connect <server>:<port> \ -cipher $c -$v < /dev/null > /dev/null 2>&1 && echo -e "$v:\t$c"
This line actually performs the tests. Because this line is contained within the inner for loop, it will run once for each combination of SSL/TLS version and supported cipher. This invokes the s_client connect command, which tells openssl to attempt a connection with the given parameters. It is best to understand this command by stepping through each parameter one at a time.
<server> is simply the IP or hostname of the target server. <port> is the target port on the target server. -cipher informs the shell that the next parameter is the cipher with which to attempt the connection. $c is bash syntax for a variable name. This value will be replaced by the current value of c determined by the state of the inner for loop (e.g.: ECDHE-RSA-AES256-GCM-SHA384). Similarly $v will be replaced by the current value of v determined by the state of the outer for loop (e.g.: tls1_1).
The next few parameters have to do with I/O redirection. < /dev/null informs the shell that the input for this command will be the output of the device /dev/null, which is a constant feed of nulls (or zeroes).
Output from a command is sent to STDOUT (standard out), which often defaults to the shell in which the command was run. In this case, > /dev/null informs the shell that STDOUT for this command should be redirected to /dev/null, essentially causing all output to be discarded. Error output from a command is typically sent to STDERR (standard error), which also often defaults to the shell in which the command was run. In this case, 2>&1 informs the shell to redirect the STDERR stream to STDOUT (standard out). This causes error output to be discarded as well, as STDOUT already points to /dev/null.
It may seem unintuitive to discard all output, but in our case, it is not necessary to view any output from this command; openssl outputs a lot of information when it executes. We do not care about most of this information. Instead, we really only care about whether or not the connection was successful.
All bash commands must finish execution by returning a value. We will be using this value to determine whether or not the connection was successful. As outlined in the openssl man pages, a non-zero return value indicates failure, whereas a return value of zero indicates success. Typically, a simple bash if structure could be used to test the return value. However, we will use a simpler method.
The next part of this line is the && operator. This is a special bash operator that can be read as "if the previous command succeeded, continue; if not, stop." This is determined by checking for a return value of zero. In our case, this reads "if the connection was successful, continue; if not, stop." So, if the connection failed, (i.e., either the cipher is not supported or the TLS/SSL version is not supported), then nothing will happen. No output will be printed, and the loop will proceed to the next cipher.
If the connection succeeded, the final part of this line will execute. This part makes use of the echo command, which is typically used to simply display a line of text. We make use of the -e flag to tell echo to enable interpretation of backslash escapes, allowing us to create tabs in our output using \t. echo accepts a string to print. In our case, the string is '$v:\t$c'. As mentioned earlier, the $v will be replaced with the current SSL/TLS version, and the $c will be replaced with the current cipher. Using our previous examples, the resulting string would be tls1_1: ECDHE-RSA-AES256-GCM-SHA384.
In summary of this line, if a given SSL/TLS version and cipher combination results in a failed connection, nothing will be printed. If a combination results in a successful connection, the SSL/TLS version will be printed, followed by a colon and a tab, and finally the cipher.
The final two lines each consist of the word done. This is simply proper syntax for closing bash for loops.
All supported ciphers, as well as all major SSL/TLS versions, are covered by this command. After running, the user will be presented with a list of all supported TLS and cipher combinations. For a list of ISE-recommended ciphers and SSL/TLS versions, seeĀ https://www.ise.io/ssl-tls-protocol-versions-cipher-suites-use/
References
Additional Information
Readers interested in further details about this topic can reach us at: contact@ise.io
Copyright <2015> <INDEPENDENT SECURITY EVALUATORS>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.