2
0
mirror of https://github.com/mozilla/cipherscan.git synced 2024-11-26 07:53:41 +01:00

Merge pull request #60 from tomato42/tls-intolerancies

TLS intolerancies
This commit is contained in:
Julien Vehent 2015-07-16 10:30:27 -04:00
commit db4b16e50c
2 changed files with 426 additions and 1 deletions

View File

@ -63,6 +63,32 @@ fi
# RSA ciphers are put at the end to force Google servers to accept ECDSA ciphers # RSA ciphers are put at the end to force Google servers to accept ECDSA ciphers
# (probably a result of a workaround for the bug in Apple implementation of ECDSA) # (probably a result of a workaround for the bug in Apple implementation of ECDSA)
CIPHERSUITE="ALL:COMPLEMENTOFALL:+aRSA" CIPHERSUITE="ALL:COMPLEMENTOFALL:+aRSA"
# some servers are intolerant to large client hello, try a shorter list of
# ciphers with them
SHORTCIPHERSUITE=('ECDHE-ECDSA-AES128-GCM-SHA256'
'ECDHE-RSA-AES128-GCM-SHA256'
'ECDHE-RSA-AES256-GCM-SHA384'
'ECDHE-ECDSA-AES256-SHA'
'ECDHE-ECDSA-AES128-SHA'
'ECDHE-RSA-AES128-SHA'
'ECDHE-RSA-AES256-SHA'
'ECDHE-RSA-DES-CBC3-SHA'
'ECDHE-ECDSA-RC4-SHA'
'ECDHE-RSA-RC4-SHA'
'DHE-RSA-AES128-SHA'
'DHE-DSS-AES128-SHA'
'DHE-RSA-CAMELLIA128-SHA'
'DHE-RSA-AES256-SHA'
'DHE-DSS-AES256-SHA'
'DHE-RSA-CAMELLIA256-SHA'
'EDH-RSA-DES-CBC3-SHA'
'AES128-SHA'
'CAMELLIA128-SHA'
'AES256-SHA'
'CAMELLIA256-SHA'
'DES-CBC3-SHA'
'RC4-SHA'
'RC4-MD5')
# as some servers are intolerant to large client hello's (or ones that have # as some servers are intolerant to large client hello's (or ones that have
# RC4 ciphers below position 64), use the following for cipher testing in case # RC4 ciphers below position 64), use the following for cipher testing in case
# of problems # of problems
@ -119,6 +145,8 @@ unset known_certs
declare -A known_certs declare -A known_certs
unset cert_checksums unset cert_checksums
declare -A cert_checksums declare -A cert_checksums
# array with results of tolerance scans (TLS version, extensions, etc.)
declare -A tls_tolerance
# because running external commands like sleep incurs a fork penalty, we # because running external commands like sleep incurs a fork penalty, we
# first check if it is necessary # first check if it is necessary
@ -255,6 +283,20 @@ c_hash() {
done done
} }
crude_grep() {
while read line; do
if [[ $line =~ $1 ]]; then
return 0
fi
done
return 1
}
check_option_support() {
$OPENSSLBIN s_client -help 2>&1 | crude_grep "$1"
return $?
}
parse_openssl_output() { parse_openssl_output() {
# clear variables in case matching doesn't hit them # clear variables in case matching doesn't hit them
current_ocspstaple="False" current_ocspstaple="False"
@ -685,6 +727,17 @@ display_results_in_terminal() {
echo "Curves ordering: $curvesordering" echo "Curves ordering: $curvesordering"
echo "Curves fallback: $fallback_supported" echo "Curves fallback: $fallback_supported"
fi fi
echo
echo "Fallbacks required:"
for test_name in "${!tls_tolerance[@]}"; do
if [[ ${tls_tolerance[$test_name]} == "False" ]]; then
echo "$test_name config not supported, connection failed"
else
local res=(${tls_tolerance[$test_name]})
echo "$test_name no fallback req, connected: ${res[1]} ${res[2]}"
fi
done | sort
} }
display_results_in_json() { display_results_in_json() {
@ -722,7 +775,22 @@ display_results_in_json() {
if [ $TEST_CURVES == "True" ]; then if [ $TEST_CURVES == "True" ]; then
echo -n ",\"curves_fallback\":\"$fallback_supported\"" echo -n ",\"curves_fallback\":\"$fallback_supported\""
fi fi
echo '}' echo -n ',"configs":{'
ctr=0
for test_name in "${!tls_tolerance[@]}"; do
local result=(${tls_tolerance[$test_name]})
[ $ctr -gt 0 ] && echo -n ","
echo -n "\"$test_name\":{"
if [[ ${result[0]} == "False" ]]; then
echo -n "\"tolerant\":\"False\""
else
echo -n "\"tolerant\":\"True\",\"proto\":\"${result[1]}\","
echo -n "\"cipher\":\"${result[2]}\",\"trusted\":\"${result[3]}\""
fi
echo -n "}"
ctr=$((ctr+1))
done
echo '}}'
} }
test_serverside_ordering() { test_serverside_ordering() {
@ -977,6 +1045,229 @@ test_curves_fallback() {
done done
} }
test_tls_tolerance() {
#
# first test general version tolerance with all we've got (full list of
# curves, full list of ciphers, NPN, ALPN
#
declare -A tls_vers_tests
tls_vers_tests['big-TLSv1.2']=""
tls_vers_tests['big-TLSv1.1']="-no_tls1_2"
tls_vers_tests['big-TLSv1.0']="-no_tls1_2 -no_tls1_1"
tls_vers_tests['big-SSLv3']="-no_tls1_2 -no_tls1_1 -no_tls1"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
sslcommand+=" -status -nextprotoneg 'http/1.1'"
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $CIPHERSUITE"
for version in "${!tls_vers_tests[@]}"; do
ratelimit
verbose "Testing fallback with $sslcommand ${tls_vers_tests[$version]}"
local tmp=$(echo Q | $sslcommand ${tls_vers_tests[$version]} 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance[$version]="False"
else
tls_tolerance[$version]="True $current_protocol $current_cipher $current_trusted"
fi
done
# if TLS1.2 didn't succeeded, try different fallbacks
if [[ ${tls_tolerance['big-TLSv1.2']} == "False" ]]; then
#
# Try big client hello, but with a version 2 compatible format
# (openssl automatically does that when there are SSLv2 ciphers in
# cipher string and no options are specified)
#
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -connect $TARGET -cipher $CIPHERSUITE"
ratelimit
verbose "Testing fallback with $sslcommand"
local tmp=$(echo Q | $sslcommand 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-big-TLSv1.2']="False"
else
tls_tolerance['v2-big-TLSv1.2']="True $current_protocol $current_cipher $current_trusted"
fi
#
# try a smaller, but still v2 compatible Client Hello
#
OLDIFS="$IFS"
IFS=":"
local ciphers="${SHORTCIPHERSUITE[*]}"
IFS="$OLDIFS"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" -connect $TARGET -cipher $ciphers"
ratelimit
verbose "Testing fallback with $sslcommand"
local tmp=$(echo Q | $sslcommand 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-TLSv1.2']="False"
else
tls_tolerance['v2-small-TLSv1.2']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v2, small but with TLS1.1 as max version
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2"
local tmp=$(echo Q | $sslcommand -no_tls1_2 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-TLSv1.1']="False"
else
tls_tolerance['v2-small-TLSv1.1']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v2, small but with TLS1.0 as max version
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-TLSv1.0']="False"
else
tls_tolerance['v2-small-TLSv1.0']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v2, small but with SSLv3 as max version
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['v2-small-SSLv3']="False"
else
tls_tolerance['v2-small-SSLv3']="True $current_protocol $current_cipher $current_trusted"
fi
#
# use v3 format TLSv1.2 hello, small cipher list
#
OLDIFS="$IFS"
IFS=":"
local ciphers="${SHORTCIPHERSUITE[*]}"
IFS="$OLDIFS"
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $ciphers:!SSLv2"
ratelimit
verbose "Testing fallback with $sslcommand"
local tmp=$(echo Q | $sslcommand 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.2']="False"
else
tls_tolerance['small-TLSv1.2']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v3 format TLSv1.1 hello, small cipher list
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2"
local tmp=$(echo Q | $sslcommand -no_tls1_2 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.1']="False"
else
tls_tolerance['small-TLSv1.1']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v3 format TLSv1.0 hello, small cipher list
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.0']="False"
else
tls_tolerance['small-TLSv1.0']="True $current_protocol $current_cipher $current_trusted"
fi
#
# v3 format TLSv1.0 hello, small cipher list, no extensions
#
if check_option_support "-no_tlsext"; then
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1 -no_tlsext"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 -no_tlsext 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-TLSv1.0-notlsext']="False"
else
tls_tolerance['small-TLSv1.0-notlsext']="True $current_protocol $current_cipher $current_trusted"
fi
fi
#
# v3 format SSLv3 hello, small cipher list
#
ratelimit
verbose "Testing fallback with $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1"
local tmp=$(echo Q | $sslcommand -no_tls1_2 -no_tls1_1 -no_tls1 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "Negotiated proto: $current_protocol, cipher: $current_cipher"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == '0000' ]]; then
tls_tolerance['small-SSLv3']="False"
else
tls_tolerance['small-SSLv3']="True $current_protocol $current_cipher $current_trusted"
fi
fi
}
# If no options are given, give usage information and exit (with error code) # If no options are given, give usage information and exit (with error code)
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
usage; usage;
@ -1120,6 +1411,8 @@ if [[ ${#cipherspref[@]} -eq 0 ]] || [[ ${pref[1]} == "SSLv2" ]]; then
get_cipher_pref "$CIPHERS" get_cipher_pref "$CIPHERS"
fi fi
test_tls_tolerance
test_serverside_ordering test_serverside_ordering
if [[ $TEST_CURVES == "True" ]]; then if [[ $TEST_CURVES == "True" ]]; then

View File

@ -13,6 +13,7 @@ path = "./results/"
import json import json
import sys import sys
from collections import defaultdict from collections import defaultdict
import operator
import os import os
import re import re
@ -94,6 +95,46 @@ eccfallback = defaultdict(int)
eccordering = defaultdict(int) eccordering = defaultdict(int)
ecccurve = defaultdict(int) ecccurve = defaultdict(int)
ocspstaple = defaultdict(int) ocspstaple = defaultdict(int)
fallbacks = defaultdict(int)
# array with indexes of fallback names for the matrix report
fallback_ids = defaultdict(int)
i=0
fallback_ids['big-SSLv3'] = i
i+=1
fallback_ids['big-TLSv1.0'] = i
i+=1
fallback_ids['big-TLSv1.1'] = i
i+=1
fallback_ids['big-TLSv1.2'] = i
i+=1
# padding space
fallback_ids[' '] = i
i+=1
fallback_ids['small-SSLv3'] = i
i+=1
fallback_ids['small-TLSv1.0-notlsext'] = i
i+=1
fallback_ids['small-TLSv1.0'] = i
i+=1
fallback_ids['small-TLSv1.1'] = i
i+=1
fallback_ids['small-TLSv1.2'] = i
i+=1
# 2nd padding space
fallback_ids[' '] = i
i+=1
fallback_ids['v2-small-SSLv3'] = i
i+=1
fallback_ids['v2-small-TLSv1.0'] = i
i+=1
fallback_ids['v2-small-TLSv1.1'] = i
i+=1
fallback_ids['v2-small-TLSv1.2'] = i
i+=1
fallback_ids['v2-big-TLSv1.2'] = i
i+=1
# 3rd padding space
fallback_ids[' '] = i
dsarsastack = 0 dsarsastack = 0
total = 0 total = 0
for r,d,flist in os.walk(path): for r,d,flist in os.walk(path):
@ -111,6 +152,7 @@ for r,d,flist in os.walk(path):
tempeccfallback = "unknown" tempeccfallback = "unknown"
tempeccordering = "unknown" tempeccordering = "unknown"
tempecccurve = {} tempecccurve = {}
tempfallbacks = {}
""" supported ciphers by the server under scan """ """ supported ciphers by the server under scan """
tempcipherstats = {} tempcipherstats = {}
ciphertypes = 0 ciphertypes = 0
@ -165,8 +207,31 @@ for r,d,flist in os.walk(path):
except ValueError: except ValueError:
continue continue
""" discard files with empty results """ """ discard files with empty results """
if len(results['ciphersuite']) < 1: if len(results['ciphersuite']) < 1:
# if there are no results from regular scan but there are
# from fallback attempts that means that the scan of a host
# is inconclusive
if 'configs' in results:
tolerance = [' '] * len(fallback_ids)
for entry in results['configs']:
config = results['configs'][entry]
if config['tolerant'] == "True" and \
config['trusted'] == "True":
# save which protocols passed
if entry in fallback_ids:
tolerance[fallback_ids[entry]] = 'v'
else:
fallback_ids[entry] = len(fallback_ids)
tolerance.insert(fallback_ids[entry], 'v')
# analysis of host won't be continued, so we have to add
# results to the permanent, not temporary table, but
# do that only when there actually were detected values
if "".join(tolerance).strip():
fallbacks["".join(tolerance).rstrip()] += 1
continue continue
""" save ECC fallback (new format) """ """ save ECC fallback (new format) """
@ -184,6 +249,56 @@ for r,d,flist in os.walk(path):
if len(results['curve']) == 1: if len(results['curve']) == 1:
tempecccurve[curve + ' Only'] = 1 tempecccurve[curve + ' Only'] = 1
if 'configs' in results:
tolerance = [' '] * len(fallback_ids)
for entry in results['configs']:
config = results['configs'][entry]
if not entry in fallback_ids:
fallback_ids[entry] = len(fallback_ids)
tolerance.insert(fallback_ids[entry], ' ')
if config['tolerant'] == "True":
tolerance[fallback_ids[entry]] = 'v'
else:
tolerance[fallback_ids[entry]] = 'X'
tempfallbacks["".join(tolerance).rstrip()] = 1
configs = results['configs']
try:
if configs['big-TLSv1.1']['tolerant'] != "True" and \
configs['big-TLSv1.2']['tolerant'] != "True" and \
configs['small-TLSv1.1']['tolerant'] != "True" and \
configs['small-TLSv1.2']['tolerant'] != "True":
if configs['v2-small-TLSv1.1']['tolerant'] != "True" and \
configs['v2-small-TLSv1.2']['tolerant'] != "True":
tempfallbacks['TLSv1.1+ strict Intolerance'] = 1
else:
tempfallbacks['TLSv1.1+ Intolerant'] = 1
if configs['big-TLSv1.1']['tolerant'] == "True" and \
configs['big-TLSv1.2']['tolerant'] != "True" and \
configs['small-TLSv1.1']['tolerant'] == "True" and \
configs['small-TLSv1.2']['tolerant'] != "True":
if configs['v2-small-TLSv1.2']['tolerant'] != "True":
tempfallbacks['TLSv1.2 strict Intolerance'] = 1
else:
tempfallbacks['TLSv1.2 Intolerant'] = 1
if configs['big-TLSv1.2']['tolerant'] != "True" and \
configs['big-TLSv1.1']['tolerant'] == "True" and \
configs['small-TLSv1.2']['tolerant'] == "True":
tempfallbacks['TLSv1.2 big Intolerance'] = 1
if configs['big-TLSv1.2']['tolerant'] != "True" and \
configs['small-TLSv1.0']['tolerant'] != "True" and \
configs['small-TLSv1.0-notlsext']['tolerant'] == "True":
tempfallbacks['TLS extension Intolerance'] = 1
if configs['big-TLSv1.2']['tolerant'] != "True" and \
configs['big-TLSv1.1']['tolerant'] != "True" and \
configs['big-TLSv1.0']['tolerant'] != "True" and \
(configs['small-TLSv1.2']['tolerant'] == "True" or
configs['v2-small-TLSv1.2']['tolerant'] == "True"):
tempfallbacks['Big handshake intolerance'] = 1
except KeyError:
pass
""" loop over list of ciphers """ """ loop over list of ciphers """
for entry in results['ciphersuite']: for entry in results['ciphersuite']:
@ -392,6 +507,9 @@ for r,d,flist in os.walk(path):
client_RC4_Pref[client_name] = True client_RC4_Pref[client_name] = True
break break
for s in tempfallbacks:
fallbacks[s] += 1
for s in tempsigstats: for s in tempsigstats:
sigalg[s] += 1 sigalg[s] += 1
@ -650,3 +768,17 @@ print("-------------------------+---------+-------")
for stat in sorted(protocolstats): for stat in sorted(protocolstats):
percent = round(protocolstats[stat] / total * 100, 4) percent = round(protocolstats[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(25) + " " + str(protocolstats[stat]).ljust(10) + str(percent).ljust(4) + "\n") sys.stdout.write(stat.ljust(25) + " " + str(protocolstats[stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\nRequired fallbacks Count Percent")
print("----------------------------------------+---------+-------")
print("big small v2 ")
print("----+-----+-----+------------------------+---------+-------")
for stat in sorted(fallbacks):
percent = round(fallbacks[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(40) + " " + str(fallbacks[stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\nFallback column names")
print("------------------------")
fallback_ids_sorted=sorted(fallback_ids.items(), key=operator.itemgetter(1))
for touple in fallback_ids_sorted:
print(str(touple[1]+1).rjust(3) + " " + str(touple[0]))