#!/usr/bin/env bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # Author: Julien Vehent [:ulfr] - 2013 DOBENCHMARK=0 BENCHMARKITER=30 OPENSSLBIN="$(dirname $0)/openssl" TIMEOUT=10 CIPHERSUITE="ALL:COMPLEMENTOFALL" VERBOSE=0 ALLCIPHERS=0 OUTPUTFORMAT="terminal" REQUEST="GET / HTTP/1.1 Host: $TARGET Connection: close " usage() { echo -e "usage: $0 [-a|--allciphers] [-b|--benchmark] [-j|--json] [-v|--verbose] [-o|--openssl file] [openssl s_client args] usage: $0 -h|--help $0 attempts to connect to a target site using all the ciphersuites it knowns. Julien Vehent [:ulfr] - https://github.com/jvehent/cipherscan Port defaults to 443 example: $ $0 www.google.com:443 Use one of the options below: -a | --allciphers Test all known ciphers individually at the end. -b | --benchmark Activate benchmark mode. -h | --help Shows this help text. -j | --json Output results in JSON format. -o | --openssl path/to/your/openssl binary you want to use. -v | --verbose Increase verbosity. The rest of the arguments will be interpreted as openssl s_client argument. This enables checking smtp/imap/pop3/ftp/xmpp via -starttls EXAMPLES: $0 -starttls xmpp jabber.ccc.de:5222 " } verbose() { if [ $VERBOSE != 0 ];then echo $@ fi } # Connect to a target host with the selected ciphersuite test_cipher_on_target() { local sslcommand=$@ cipher="" protocols="" pfs="" for tls_version in "-ssl2" "-ssl3" "-tls1" "-tls1_1" "-tls1_2" do # echo "$sslcommand $tls_version" local tmp=$($sslcommand $tls_version 1>/dev/stdout 2>/dev/null << EOF $REQUEST EOF ) current_cipher=$(grep "New, " <<<"$tmp"|awk '{print $5}') current_pfs=$(grep 'Server Temp Key' <<<"$tmp"|awk '{print $4$5$6$7}') current_protocol=$(egrep "^\s+Protocol\s+:" <<<"$tmp"|awk '{print $3}') if [[ -z "$current_protocol" || "$current_cipher" == '(NONE)' ]]; then # connection failed, try again with next TLS version continue fi # connection succeeded, add TLS version to positive results if [ -z "$protocols" ]; then protocols=$current_protocol else protocols="$protocols,$current_protocol" fi cipher=$current_cipher pfs=$current_pfs # grab the cipher and PFS key size done # if cipher is empty, that means none of the TLS version worked with # the current cipher if [ -z "$cipher" ]; then verbose "handshake failed, no ciphersuite was returned" result='ConnectionFailure' return 2 # if cipher contains NONE, the cipher wasn't accepted elif [ "$cipher" == '(NONE) ' ]; then result="$cipher $protocols $pfs" verbose "handshake failed, server returned ciphersuite '$result'" return 1 # the connection succeeded else result="$cipher $protocols $pfs" verbose "handshake succeeded, server returned ciphersuite '$result'" return 0 fi } # Calculate the average handshake time for a specific ciphersuite bench_cipher() { local ciphersuite="$1" local sslcommand='echo "quit\n" | $OPENSSLBIN s_client $SCLIENTARGS -connect $TARGET -cipher $ciphersuite' local t="$(date +%s%N)" verbose "Benchmarking handshake on '$TARGET' with ciphersuite '$ciphersuite'" for i in $(seq 1 $BENCHMARKITER); do ($sslcommand 2>/dev/null 1>/dev/null) << EOF $REQUEST EOF if [ $? -gt 0 ]; then break fi done # Time interval in nanoseconds local t="$(($(date +%s%N) - t))" verbose "Benchmarking done in $t nanoseconds" # Microseconds cipherbenchms="$((t/1000/$BENCHMARKITER))" } # Connect to the target and retrieve the chosen cipher # recursively until the connection fails get_cipher_pref() { [ "$OUTPUTFORMAT" == "terminal" ] && echo -n '.' local ciphersuite="$1" local sslcommand="$OPENSSLBIN s_client $SCLIENTARGS -connect $TARGET -cipher $ciphersuite" verbose "Connecting to '$TARGET' with ciphersuite '$ciphersuite'" test_cipher_on_target "$sslcommand" local success=$? # If the connection succeeded with the current cipher, benchmark and store if [ $success -eq 0 ]; then cipherspref=("${cipherspref[@]}" "$result") pciph=$(echo $result|awk '{print $1}') get_cipher_pref "!$pciph:$ciphersuite" return 0 fi } display_results_in_terminal() { # Display the results ctr=1 for cipher in "${cipherspref[@]}"; do pciph=$(echo $cipher|awk '{print $1}') if [ $DOBENCHMARK -eq 1 ]; then bench_cipher "$pciph" r="$ctr $cipher $cipherbenchms" else r="$ctr $cipher" fi results=("${results[@]}" "$r") ctr=$((ctr+1)) done if [ $DOBENCHMARK -eq 1 ]; then header="prio ciphersuite protocols pfs_keysize avg_handshake_microsec" else header="prio ciphersuite protocols pfs_keysize" fi ctr=0 for result in "${results[@]}"; do if [ $ctr -eq 0 ]; then echo $header ctr=$((ctr+1)) fi echo $result|grep -v '(NONE)' done|column -t } display_results_in_json() { # Display the results in json ctr=0 echo -n "{\"target\":\"$TARGET\",\"date\":\"$(date -R)\",\"ciphersuite\": [" for cipher in "${cipherspref[@]}"; do [ $ctr -gt 0 ] && echo -n ',' echo -n "{\"cipher\":\"$(echo $cipher|awk '{print $1}')\"," echo -n "\"protocols\":[\"$(echo $cipher|awk '{print $2}'|sed 's/,/","/g')\"]," pfs=$(echo $cipher|awk '{print $3}') [ "$pfs" == "" ] && pfs="None" echo -n "\"pfs\":\"$pfs\"}" ctr=$((ctr+1)) done echo ']}' } # UNKNOWNOPTIONS="" while : do case $1 in -h | --help | -\?) usage exit 0 # This is not an error, User asked help. Don't do "exit 1" ;; -o | --openssl) OPENSSLBIN=$2 # You might want to check if you really got FILE shift 2 ;; -a | --allciphers) ALLCIPHERS=1 shift ;; -v | --verbose) # Each instance of -v adds 1 to verbosity VERBOSE=$((VERBOSE+1)) shift ;; -j | -json | --json | --JSON) OUTPUTFORMAT="json" shift ;; -b | --benchmark) DOBENCHMARK=1 shift ;; --) # End of all options shift break ;; # -*) # UNKNOWNOPTIONS=$((UNKNOWNOPTIONS+$1)) # # echo "WARN: Unknown option (ignored): $1" >&2 # shift # ;; *) # no more options we understand. break ;; esac done if [ VERBOSE != 0 ] ; then echo "Loading $($OPENSSLBIN ciphers -v $CIPHERSUITE 2>/dev/null|grep Kx|wc -l) ciphersuites from $(echo -n $($OPENSSLBIN version 2>/dev/null))" $OPENSSLBIN ciphers ALL 2>/dev/null fi #[[ -z $1 || "$1" == "-h" || "$1" == "--help" ]] && usage # if [ ! -z $2 ]; then # if [ "$1" == "-v" ]; then # VERBOSE=1 # echo "Loading $($OPENSSLBIN ciphers -v $CIPHERSUITE 2>/dev/null|grep Kx|wc -l) ciphersuites from $(echo -n $($OPENSSLBIN version 2>/dev/null))" # $OPENSSLBIN ciphers ALL 2>/dev/null # elif [ "$1" == "-a" ]; then # ALLCIPHERS=1 # elif [ "$1" == "-json" ]; then # OUTPUTFORMAT="json" # fi # fi # echo paramters left: $@ TEMPTARGET=$(sed -e 's/^.* //'<<<"${@}") HOST=$(sed -e 's/:.*//'<<<"${TEMPTARGET}") PORT=$(sed -e 's/.*://'<<<"${TEMPTARGET}") # Default to https if no port given if [ "$HOST" = "$PORT" ]; then PORT=443 fi # echo host: $HOST # echo port: $PORT TARGET=$HOST:$PORT # echo target: $TARGET SCLIENTARGS=$(sed -e s,${TEMPTARGET},,<<<"${@}") # echo sclientargs: $SCLIENTARGS cipherspref=(); results=() # Call to the recursive loop that retrieves the cipher preferences get_cipher_pref $CIPHERSUITE if [ "$OUTPUTFORMAT" == "json" ]; then display_results_in_json else echo display_results_in_terminal fi # If asked, test every single cipher individually if [ $ALLCIPHERS -gt 0 ]; then echo; echo "All accepted ciphersuites" for c in $($OPENSSLBIN ciphers -v ALL:COMPLEMENTOFALL 2>/dev/null |awk '{print $1}'|sort|uniq); do r="fail" osslcommand='echo "quit\n" | $OPENSSLBIN s_client $SCLIENTARGS -connect $TARGET -cipher $c' test_cipher_on_target "$osslcommand" if [ $? -eq 0 ]; then r="pass" fi echo "$c $r"|awk '{printf "%-35s %s\n",$1,$2}' done fi