Workarounds for missing curves in OpenSSL

In case where the OpenSSL version used cannot successfully do openssl s_client
connects there are a few problems, see #1087.

This PR partly addresses them by
* changing the logic of HTTP header failure: we don't terminate anymore but
  continue with a warning message
* we try to find out what the reason was: If it is a missing curve we signal
  it back to the user
* we keep track in a global variable KNOWN_OSSL_PROB. It's not being used yet
  on all connects as it has not been decided whether we do a connect despite
  we know if there's a problem or rather not.
* Give hints to the user for resumption tests, secure renegotiation, CRIME and BREACH.
  For the latter --assume-http needs to be supplied for any output.

Also: for finding the OPTIMAL_PROTO now (unless --ssl-native is being used)
sockets are the default which removes in cases where an openssl s_client
connect fails, the initial message 'doesn't seem to be a TLS/SSL enabled server'
and prompt 'Really proceed ? ("yes" to continue)'. For STARTTLS this needs
to be done as well.

Here a minor bug was fixed: when openssl s_client connect in determine_optimal_proto()
succeeded without a protocol supplied, OPTIMAL_PROTO wasn't set. A statement was
added but now it is only being used when --ssl-native was supplied.

Leftover for this workaround is to find out why the number of certificate retrieved is
zero in those cases, despite the fact that there's a valid 'host_certificate.pem' from
tls_socket() calls.  Thus still run_server_defaults() stops after 'TLS clock skew'
as certificate_info() is not being called in run_server_defaults(). For now in
those cases 'Problem: Host certificate found but we can't continue with "server defaults"'
is being printed.

In general for the future it would be great if we could e.g. retrieve the header over
TLS sockets.
This commit is contained in:
Dirk Wetter 2019-02-11 19:49:50 +01:00
parent 691ca28bb9
commit 5d1109a582

View File

@ -232,7 +232,7 @@ IGN_OCSP_PROXY=${IGN_OCSP_PROXY:-false} # Also when --proxy is supplied it is ig
HEADER_MAXSLEEP=${HEADER_MAXSLEEP:-5} # we wait this long before killing the process to retrieve a service banner / http header HEADER_MAXSLEEP=${HEADER_MAXSLEEP:-5} # we wait this long before killing the process to retrieve a service banner / http header
MAX_SOCKET_FAIL=${MAX_SOCKET_FAIL:-2} # If this many failures for TCP socket connects are reached we terminate MAX_SOCKET_FAIL=${MAX_SOCKET_FAIL:-2} # If this many failures for TCP socket connects are reached we terminate
MAX_OSSL_FAIL=${MAX_OSSL_FAIL:-2} # If this many failures for s_client connects are reached we terminate MAX_OSSL_FAIL=${MAX_OSSL_FAIL:-2} # If this many failures for s_client connects are reached we terminate
MAX_HEADER_FAIL=${MAX_HEADER_FAIL:-3} # If this many failures for HTTP GET are encountered we terminate MAX_HEADER_FAIL=${MAX_HEADER_FAIL:-2} # If this many failures for HTTP GET are encountered we don't try again to get the header
MAX_WAITSOCK=${MAX_WAITSOCK:-10} # waiting at max 10 seconds for socket reply. There shouldn't be any reason to change this. MAX_WAITSOCK=${MAX_WAITSOCK:-10} # waiting at max 10 seconds for socket reply. There shouldn't be any reason to change this.
CCS_MAX_WAITSOCK=${CCS_MAX_WAITSOCK:-5} # for the two CCS payload (each). There shouldn't be any reason to change this. CCS_MAX_WAITSOCK=${CCS_MAX_WAITSOCK:-5} # for the two CCS payload (each). There shouldn't be any reason to change this.
HEARTBLEED_MAX_WAITSOCK=${HEARTBLEED_MAX_WAITSOCK:-8} # for the heartbleed payload. There shouldn't be any reason to change this. HEARTBLEED_MAX_WAITSOCK=${HEARTBLEED_MAX_WAITSOCK:-8} # for the heartbleed payload. There shouldn't be any reason to change this.
@ -284,6 +284,8 @@ NR_SOCKET_FAIL=0 # Counter for socket failures
NR_OSSL_FAIL=0 # .. for OpenSSL connects NR_OSSL_FAIL=0 # .. for OpenSSL connects
NR_HEADER_FAIL=0 # .. for HTTP_GET NR_HEADER_FAIL=0 # .. for HTTP_GET
PROTOS_OFFERED="" # This keeps which protocol is being offered. See has_server_protocol(). PROTOS_OFFERED="" # This keeps which protocol is being offered. See has_server_protocol().
CURVES_OFFERED="" # This keeps which curves have been detected. Just for error handling
KNOWN_OSSL_PROB=false # We need OpenSSL a few times. This variable is an indicator if we can't connect. Eases handling
DETECTED_TLS_VERSION="" DETECTED_TLS_VERSION=""
TLS_EXTENSIONS="" TLS_EXTENSIONS=""
declare -r NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1" declare -r NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1"
@ -1959,6 +1961,8 @@ run_http_header() {
local header local header
local referer useragent local referer useragent
local url redirect local url redirect
local jsonID="HTTP_status_code"
local spaces=" "
HEADERFILE=$TEMPDIR/$NODEIP.http_header.txt HEADERFILE=$TEMPDIR/$NODEIP.http_header.txt
if [[ $NR_HEADER_FAIL -eq 0 ]]; then if [[ $NR_HEADER_FAIL -eq 0 ]]; then
@ -1966,16 +1970,16 @@ run_http_header() {
outln; pr_headlineln " Testing HTTP header response @ \"$URL_PATH\" " outln; pr_headlineln " Testing HTTP header response @ \"$URL_PATH\" "
outln outln
fi fi
if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then
# signal to caller we have a problem
return 1
fi
pr_bold " HTTP Status Code "
[[ -z "$1" ]] && url="/" || url="$1" [[ -z "$1" ]] && url="/" || url="$1"
if [[ "$SOCKETHEADER" == true ]]; then if [[ "$SOCKETHEADER" == true ]]; then
# This is just for testing only. It doesn't work (yet) :
tls_sockets "03" "$TLS12_CIPHER" "" "" "" false #FIXME: would be great to complete the handshake and then e.g. tunnel HTTP over it
debugme echo "--> $?"
printf -- "%b" "$GET_REQ11" >&5 # This GET request is not being logged on the server side --> probably we're still on the TLS layer
cat <&5 >$HEADERFILE
debugme xxd "$HEADERFILE" # 1503 -> TLS alert
close_socket
else else
printf "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE & printf "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE &
wait_kill $! $HEADER_MAXSLEEP wait_kill $! $HEADER_MAXSLEEP
@ -1998,26 +2002,38 @@ run_http_header() {
fileout "HTTP_status_code" "WARN" "HTTP header request failed" fileout "HTTP_status_code" "WARN" "HTTP header request failed"
debugme cat $HEADERFILE debugme cat $HEADERFILE
((NR_HEADER_FAIL++)) ((NR_HEADER_FAIL++))
connectivity_problem $NR_HEADER_FAIL $MAX_HEADER_FAIL "HTTP header connect problem" "repeated HTTP header connect problems, doesn't make sense to continue"
return 1
fi fi
fi fi
fi fi
if [[ ! -s $HEADERFILE ]]; then if [[ ! -s $HEADERFILE ]]; then
prln_warning " HTTP header reply empty"
fileout "HTTP_status_code" "WARN" "HTTP header reply empty"
((NR_HEADER_FAIL++)) ((NR_HEADER_FAIL++))
connectivity_problem $NR_HEADER_FAIL $MAX_HEADER_FAIL "HTTP header zero" "repeatedly HTTP header was zero, doesn't make sense to continue" if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then
# Now, try to give a hint whether it would make sense to try with OpenSSL 1.1.0 or 1.1.1 instead
if [[ $CURVES_OFFERED == X448 ]] && ! "$HAS_X448" ; then
generic_nonfatal "HTTP header was repeatedly zero due to missing X448 curve." "${spaces}OpenSSL 1.1.1 might help. Skipping complete HTTP header section."
elif [[ $CURVES_OFFERED == X25519 ]] && ! "$HAS_X25519" ; then
generic_nonfatal "HTTP header was repeatedly zero due to missing X25519 curve." "${spaces}OpenSSL 1.1.0 might help. Skipping complete HTTP header section."
elif [[ $CURVES_OFFERED =~ X25519 ]] && [[ $CURVES_OFFERED =~ X448 ]] && ! "$HAS_X25519" && ! "$HAS_X448"; then
generic_nonfatal "HTTP header was repeatedly zero due to missing X25519/X448 curves." "${spaces}OpenSSL >=1.1.0 might help. Skipping complete HTTP header section."
else
# we could give more hints but these are the most likely cases
generic_nonfatal "HTTP header was repeatedly zero." "Skipping complete HTTP header section."
fi
KNOWN_OSSL_PROB=true
return 1 return 1
else
pr_warning "HTTP header reply empty. "
fileout "$jsonID" "WARN" "HTTP header reply empty"
fi
fi fi
# populate vars for HTTP time # Populate vars for HTTP time
debugme echo "$NOW_TIME: $HTTP_TIME" debugme echo "$NOW_TIME: $HTTP_TIME"
# delete from pattern til the end. We ignore any leading spaces (e.g. www.amazon.de) # delete from pattern til the end. We ignore any leading spaces (e.g. www.amazon.de)
sed -e '/<HTML>/,$d' -e '/<html>/,$d' -e '/<\!DOCTYPE/,$d' -e '/<\!doctype/,$d' \ sed -e '/<HTML>/,$d' -e '/<html>/,$d' -e '/<\!DOCTYPE/,$d' -e '/<\!doctype/,$d' \
-e '/<XML/,$d' -e '/<xml/,$d' -e '/<\?XML/,$d' -e '/<?xml/,$d' $HEADERFILE >$HEADERFILE.tmp -e '/<XML/,$d' -e '/<xml/,$d' -e '/<\?XML/,$d' -e '/<?xml/,$d' $HEADERFILE >$HEADERFILE.tmp
# ^^^ Attention: the filtering for the html body only as of now, doesn't work for other content yet # ^^^ Attention: filtering is for html body only as of now, doesn't work for other content yet
mv $HEADERFILE.tmp $HEADERFILE mv $HEADERFILE.tmp $HEADERFILE
HTTP_STATUS_CODE=$(awk '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) HTTP_STATUS_CODE=$(awk '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE)
@ -2025,14 +2041,12 @@ run_http_header() {
msg_thereafter=$(strip_lf "$msg_thereafter") # field separator, otherwise we need a loop with awk msg_thereafter=$(strip_lf "$msg_thereafter") # field separator, otherwise we need a loop with awk
debugme echo "Status/MSG: $HTTP_STATUS_CODE $msg_thereafter" debugme echo "Status/MSG: $HTTP_STATUS_CODE $msg_thereafter"
pr_bold " HTTP Status Code " [[ -n "$HTTP_STATUS_CODE" ]] && out " $HTTP_STATUS_CODE$msg_thereafter"
jsonID="HTTP_status_code"
out " $HTTP_STATUS_CODE$msg_thereafter"
case $HTTP_STATUS_CODE in case $HTTP_STATUS_CODE in
301|302|307|308) 301|302|307|308)
redirect=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n') redirect=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n')
out ", redirecting to \""; pr_url "$redirect"; out "\"" out ", redirecting to \""; pr_url "$redirect"; out "\""
if [[ $redirect == "http://"* ]]; then if [[ $redirect =~ http:// ]]; then
pr_svrty_high " -- Redirect to insecure URL (NOT ok)" pr_svrty_high " -- Redirect to insecure URL (NOT ok)"
fileout "insecure_redirect" "HIGH" "Redirect to insecure URL: \"$redirect\"" fileout "insecure_redirect" "HIGH" "Redirect to insecure URL: \"$redirect\""
fi fi
@ -2059,7 +2073,7 @@ run_http_header() {
fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")" fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")"
;; ;;
"") "")
pr_warning ". No HTTP status code??" prln_warning "No HTTP status code."
fileout "$jsonID" "WARN" "No HTTP status code" fileout "$jsonID" "WARN" "No HTTP status code"
return 1 return 1
;; ;;
@ -5791,7 +5805,11 @@ sub_session_resumption() {
$OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_out $sess_data") </dev/null &>/dev/null $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_out $sess_data") </dev/null &>/dev/null
ret1=$? ret1=$?
if "$byID" && [[ $OSSL_VER_MINOR == "1.1" ]] && [[ $OSSL_VER_MAJOR == "1" ]] && [[ ! -s "$sess_data" ]]; then if [[ $ret1 -ne 0 ]]; then
debugme echo -n "Couldn't connect #1 "
return 7
fi
if "$byID" && [[ $OSSL_VER_MINOR == 1.1 ]] && [[ $OSSL_VER_MAJOR == 1 ]] && [[ ! -s "$sess_data" ]]; then
# it seems OpenSSL indicates no Session ID resumption by just not generating output # it seems OpenSSL indicates no Session ID resumption by just not generating output
debugme echo -n "No session resumption byID (empty file)" debugme echo -n "No session resumption byID (empty file)"
ret=2 ret=2
@ -5802,6 +5820,10 @@ sub_session_resumption() {
echo -n "$ret1, $ret2, " echo -n "$ret1, $ret2, "
[[ -s "$sess_data" ]] && echo "not empty" || echo "empty" [[ -s "$sess_data" ]] && echo "not empty" || echo "empty"
fi fi
if [[ $ret2 -ne 0 ]]; then
debugme echo -n "Couldn't connect #2 "
return 7
fi
# now get the line and compare the numbers read" and "written" as a second criteria. # now get the line and compare the numbers read" and "written" as a second criteria.
rw_line="$(awk '/^SSL handshake has read/ { print $5" "$(NF-1) }' "$tmpfile" )" rw_line="$(awk '/^SSL handshake has read/ { print $5" "$(NF-1) }' "$tmpfile" )"
rw_line=($rw_line) rw_line=($rw_line)
@ -5821,7 +5843,7 @@ sub_session_resumption() {
ret=0 ret=0
else else
debugme echo -n "unclear status: $ret1, $ret2, $new_sid, $new_sid2 -- " debugme echo -n "unclear status: $ret1, $ret2, $new_sid, $new_sid2 -- "
ret=7 ret=5
fi fi
if [[ $DEBUG -ge 2 ]]; then if [[ $DEBUG -ge 2 ]]; then
"$byID" && echo "byID" || echo "by ticket" "$byID" && echo "byID" || echo "by ticket"
@ -8228,7 +8250,6 @@ certificate_info() {
outln outln
fi fi
out "$indent"; pr_bold " Certificate Revocation List " out "$indent"; pr_bold " Certificate Revocation List "
jsonID="cert_crlDistributionPoints" jsonID="cert_crlDistributionPoints"
# ~ get next 50 lines after pattern , strip until Signature Algorithm and retrieve URIs # ~ get next 50 lines after pattern , strip until Signature Algorithm and retrieve URIs
@ -8541,12 +8562,13 @@ run_server_defaults() {
done done
determine_tls_extensions determine_tls_extensions
if [[ $? -eq 0 ]] && [[ "$OPTIMAL_PROTO" != "-ssl2" ]]; then if [[ $? -eq 0 ]] && [[ "$OPTIMAL_PROTO" != -ssl2 ]]; then
cp "$TEMPDIR/$NODEIP.determine_tls_extensions.txt" $TMPFILE cp "$TEMPDIR/$NODEIP.determine_tls_extensions.txt" $TMPFILE
>$ERRFILE >$ERRFILE
[[ -z "$sessticket_lifetime_hint" ]] && sessticket_lifetime_hint=$(awk '/session ticket lifetime/' $TMPFILE) [[ -z "$sessticket_lifetime_hint" ]] && sessticket_lifetime_hint=$(awk '/session ticket lifetime/' $TMPFILE)
fi fi
debugme echo "# certificates found $certs_found"
# Now that all of the server's certificates have been found, determine for # Now that all of the server's certificates have been found, determine for
# each certificate whether certificate transparency information is provided. # each certificate whether certificate transparency information is provided.
for (( i=1; i <= certs_found; i++ )); do for (( i=1; i <= certs_found; i++ )); do
@ -8622,13 +8644,18 @@ run_server_defaults() {
out "Tickets no, " out "Tickets no, "
fileout "$jsonID" "INFO" "not supported" fileout "$jsonID" "INFO" "not supported"
;; ;;
5) SESS_RESUMPTION[2]="ticket=noclue"
pr_warning "Ticket resumption test failed, pls report / "
fileout "$jsonID" "WARN" "check failed, pls report"
((ret++))
;;
6) SESS_RESUMPTION[2]="ticket=clientauth" 6) SESS_RESUMPTION[2]="ticket=clientauth"
pr_warning "Client Auth: Ticket resumption test not supported / " pr_warning "Client Auth: Ticket resumption test not supported / "
fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication" fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication"
;; ;;
7) SESS_RESUMPTION[2]="ticket=noclue" 7) SESS_RESUMPTION[2]="ticket=unsuccessful"
pr_warning "Ticket resumption test failed, pls report / " pr_warning "Connect problem: Ticket resumption test not possible / "
fileout "$jsonID" "WARN" "check failed, pls report" fileout "$jsonID" "WARN" "check failed because of connect problem"
((ret++)) ((ret++))
;; ;;
esac esac
@ -8649,14 +8676,19 @@ run_server_defaults() {
outln "ID: no" outln "ID: no"
fileout "$jsonID" "INFO" "not supported" fileout "$jsonID" "INFO" "not supported"
;; ;;
5) SESS_RESUMPTION[1]="ID=noclue"
prln_warning "ID resumption test failed, pls report"
fileout "$jsonID" "WARN" "check failed, pls report"
((ret++))
;;
6) SESS_RESUMPTION[1]="ID=clientauth" 6) SESS_RESUMPTION[1]="ID=clientauth"
[[ ${SESS_RESUMPTION[2]} =~ clientauth ]] || pr_warning "Client Auth: " [[ ${SESS_RESUMPTION[2]} =~ clientauth ]] || pr_warning "Client Auth: "
prln_warning "ID resumption resumption test not supported" prln_warning "ID resumption resumption test not supported"
fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication" fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication"
;; ;;
7) SESS_RESUMPTION[1]="ID=noclue" 7) SESS_RESUMPTION[1]="ID=unsuccessful"
prln_warning "ID resumption test failed, pls report" prln_warning "ID resumption test failed"
fileout "$jsonID" "WARN" "check failed, pls report" fileout "$jsonID" "WARN" "check failed because of connect problem"
((ret++)) ((ret++))
;; ;;
esac esac
@ -8700,6 +8732,9 @@ run_server_defaults() {
$OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO") 2>>$ERRFILE </dev/null | \ $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO") 2>>$ERRFILE </dev/null | \
awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT.nosni awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT.nosni
fi fi
elif [[ $certs_found -eq 0 ]] && [[ -s "$HOSTCERT" ]]; then
outln
generic_nonfatal "Problem: Host certificate found but we can't continue with \"server defaults\""
fi fi
[[ $DEBUG -ge 1 ]] && [[ -e $HOSTCERT.nosni ]] && $OPENSSL x509 -in $HOSTCERT.nosni -text -noout 2>>$ERRFILE > $HOSTCERT.nosni.txt [[ $DEBUG -ge 1 ]] && [[ -e $HOSTCERT.nosni ]] && $OPENSSL x509 -in $HOSTCERT.nosni -text -noout 2>>$ERRFILE > $HOSTCERT.nosni.txt
@ -9104,6 +9139,8 @@ run_pfs() {
fi fi
fi fi
fi fi
CURVES_OFFERED="$curves_offered"
CURVES_OFFERED=$(strip_trailing_space "$CURVES_OFFERED")
# find out what groups are supported. # find out what groups are supported.
if "$using_sockets" && ( "$pfs_tls13_offered" || "$ffdhe_offered" ); then if "$using_sockets" && ( "$pfs_tls13_offered" || "$ffdhe_offered" ); then
@ -10840,7 +10877,7 @@ check_tls_serverhellodone() {
remaining=$tls_alert_ascii_len-$i remaining=$tls_alert_ascii_len-$i
[[ $remaining -lt 4 ]] && return 1 [[ $remaining -lt 4 ]] && return 1
tls_err_level=${tls_alert_ascii:i:2} # 1: warning, 2: fatal tls_err_level=${tls_alert_ascii:i:2} # 1: warning, 2: fatal
[[ $tls_err_level == "02" ]] && DETECTED_TLS_VERSION="" && tm_out "" && return 0 [[ $tls_err_level == 02 ]] && DETECTED_TLS_VERSION="" && tm_out "" && return 0
done done
# If there is a serverHelloDone or Finished, then we are done. # If there is a serverHelloDone or Finished, then we are done.
@ -12194,8 +12231,8 @@ prepare_tls_clienthello() {
local offer_compression=false compression_methods local offer_compression=false compression_methods
# TLSv1.3 ClientHello messages MUST specify only the NULL compression method. # TLSv1.3 ClientHello messages MUST specify only the NULL compression method.
[[ "$5" == "true" ]] && [[ "0x$tls_low_byte" -le "0x03" ]] && offer_compression=true [[ "$5" == true ]] && [[ "0x$tls_low_byte" -le "0x03" ]] && offer_compression=true
[[ "$6" == "false" ]] && new_socket=false [[ "$6" == false ]] && new_socket=false
cipher_suites="$2" # we don't have the leading \x here so string length is two byte less, see next cipher_suites="$2" # we don't have the leading \x here so string length is two byte less, see next
len_ciph_suites_byte=${#cipher_suites} len_ciph_suites_byte=${#cipher_suites}
@ -13564,8 +13601,8 @@ run_renego() {
;; ;;
esac esac
else else
prln_warning "handshake didn't succeed" prln_warning "OpenSSL handshake didn't succeed"
fileout "$jsonID" "WARN" "handshake didn't succeed" "$cve" "$cwe" fileout "$jsonID" "WARN" "OpenSSL handshake didn't succeed" "$cve" "$cwe"
fi fi
# see: https://community.qualys.com/blogs/securitylabs/2011/10/31/tls-renegotiation-and-denial-of-service-attacks # see: https://community.qualys.com/blogs/securitylabs/2011/10/31/tls-renegotiation-and-denial-of-service-attacks
@ -13752,7 +13789,7 @@ run_breach() {
local hint="" local hint=""
local jsonID="BREACH" local jsonID="BREACH"
[[ $SERVICE != "HTTP" ]] && ! "$CLIENT_AUTH" && return 7 [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln
pr_bold " BREACH"; out " ($cve) " pr_bold " BREACH"; out " ($cve) "
@ -13761,15 +13798,20 @@ run_breach() {
fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe" fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe"
fi fi
# if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then
# pr_warning "Retrieving HTTP header failed before. Skipping."
# fileout "$jsonID" "WARN" "HTTP response was wampty before" "$cve" "$cwe"
# outln
# return 1
# fi
[[ -z "$url" ]] && url="/" [[ -z "$url" ]] && url="/"
disclaimer=" - only supplied \"$url\" tested" disclaimer=" - only supplied \"$url\" tested"
referer="https://google.com/" referer="https://google.com/"
[[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com [[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com
useragent="$UA_STD" useragent="$UA_STD"
$SNEAKY && useragent="$UA_SNEAKY" $SNEAKY && useragent="$UA_SNEAKY"
printf "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: gzip,deflate,compress\r\nAccept: text/*\r\n\r\n" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE & printf "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: gzip,deflate,compress\r\nAccept: text/*\r\n\r\n" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE &
wait_kill $! $HEADER_MAXSLEEP wait_kill $! $HEADER_MAXSLEEP
was_killed=$? # !=0 was killed was_killed=$? # !=0 was killed
@ -13777,12 +13819,12 @@ run_breach() {
result=$(strip_lf "$result") result=$(strip_lf "$result")
debugme grep '^Content-Encoding' $TMPFILE debugme grep '^Content-Encoding' $TMPFILE
if [[ ! -s $TMPFILE ]]; then if [[ ! -s $TMPFILE ]]; then
pr_warning "failed (HTTP header request stalled" pr_warning "failed (HTTP header request stalled or empty return"
if [[ $was_killed -ne 0 ]]; then if [[ $was_killed -ne 0 ]]; then
pr_warning " and was terminated" pr_warning " and was terminated"
fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe" fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe"
else else
fileout "$jsonID" "WARN" "Test failed as HTTP request stalled" "$cve" "$cwe" fileout "$jsonID" "WARN" "Test failed as HTTP response was empty" "$cve" "$cwe"
fi fi
prln_warning ") " prln_warning ") "
ret=1 ret=1
@ -13828,7 +13870,7 @@ run_sweet32() {
# Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here # Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here
if "$using_sockets"; then if "$using_sockets"; then
for proto in 03 02 01 00; do for proto in 03 02 01 00; do
"$FAST" && [[ "$proto" != "03" ]] && break "$FAST" && [[ "$proto" != 03 ]] && break
! "$FAST" && [[ $(has_server_protocol "$proto") -eq 1 ]] && continue ! "$FAST" && [[ $(has_server_protocol "$proto") -eq 1 ]] && continue
tls_sockets "$proto" "${sweet32_ciphers_hex}, 00,ff" tls_sockets "$proto" "${sweet32_ciphers_hex}, 00,ff"
sclient_success=$? sclient_success=$?
@ -13840,8 +13882,8 @@ run_sweet32() {
nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $sweet32_ciphers)) nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $sweet32_ciphers))
for proto in -no_ssl2 -tls1_1 -tls1 -ssl3; do for proto in -no_ssl2 -tls1_1 -tls1 -ssl3; do
[[ $nr_supported_ciphers -eq 0 ]] && break [[ $nr_supported_ciphers -eq 0 ]] && break
! "$HAS_SSL3" && [[ "$proto" == "-ssl3" ]] && continue ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue
if [[ "$proto" != "-no_ssl2" ]]; then if [[ "$proto" != -no_ssl2 ]]; then
"$FAST" && break "$FAST" && break
[[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue
fi fi
@ -16488,6 +16530,18 @@ ip_fatal() {
return 0 return 0
} }
# This gneric function outputs an error onto the screen and handles logging.
# arg1: string to print / to write to file, arg2 (optional): additional hint to write
#
generic_nonfatal() {
prln_magenta "$1" >&2
[[ -n $2 ]] && outln "$2"
[[ -n "$LOGFILE" ]] && prln_magenta "$1" >>$LOGFILE && [[ -n $2 ]] && outln "$2" >>$LOGFILE
outln
fileout "scanProblem" "WARN" "$1"
return 0
}
initialize_engine(){ initialize_engine(){
# for now only GOST engine # for now only GOST engine
grep -q '^# testssl config file' "$OPENSSL_CONF" 2>/dev/null && \ grep -q '^# testssl config file' "$OPENSSL_CONF" 2>/dev/null && \
@ -17031,7 +17085,7 @@ sclient_auth() {
} }
# this function determines OPTIMAL_PROTO. It is a workaround function as under certain circumstances # This function determines OPTIMAL_PROTO. It is a workaround function as under certain circumstances
# (e.g. IIS6.0 and openssl 1.0.2 as opposed to 1.0.1) needs a protocol otherwise s_client -connect will fail! # (e.g. IIS6.0 and openssl 1.0.2 as opposed to 1.0.1) needs a protocol otherwise s_client -connect will fail!
# Circumstances observed so far: 1.) IIS 6 2.) starttls + dovecot imap # Circumstances observed so far: 1.) IIS 6 2.) starttls + dovecot imap
# The first try in the loop is empty as we prefer not to specify always a protocol if we can get along w/o it # The first try in the loop is empty as we prefer not to specify always a protocol if we can get along w/o it
@ -17039,8 +17093,12 @@ sclient_auth() {
determine_optimal_proto() { determine_optimal_proto() {
local all_failed=true local all_failed=true
local tmp="" local tmp=""
local proto=""
local using_sockets=true
>$ERRFILE >$ERRFILE
"$SSL_NATIVE" && using_sockets=false
if [[ -n "$1" ]]; then if [[ -n "$1" ]]; then
# starttls workaround needed see https://github.com/drwetter/testssl.sh/issues/188 -- kind of odd # starttls workaround needed see https://github.com/drwetter/testssl.sh/issues/188 -- kind of odd
for STARTTLS_OPTIMAL_PROTO in -tls1_2 -tls1 -ssl3 -tls1_1 -tls1_3 -ssl2; do for STARTTLS_OPTIMAL_PROTO in -tls1_2 -tls1 -ssl3 -tls1_1 -tls1_3 -ssl2; do
@ -17050,6 +17108,7 @@ determine_optimal_proto() {
-ssl2) "$HAS_SSL2" || continue ;; -ssl2) "$HAS_SSL2" || continue ;;
*) ;; *) ;;
esac esac
#FIXME: to be replaced / added by socket ( if "$using_sockets" ...)
$OPENSSL s_client $(s_client_options "$STARTTLS_OPTIMAL_PROTO $BUGS -connect "$NODEIP:$PORT" $PROXY -msg -starttls $1") </dev/null >$TMPFILE 2>>$ERRFILE $OPENSSL s_client $(s_client_options "$STARTTLS_OPTIMAL_PROTO $BUGS -connect "$NODEIP:$PORT" $PROXY -msg -starttls $1") </dev/null >$TMPFILE 2>>$ERRFILE
if sclient_auth $? $TMPFILE; then if sclient_auth $? $TMPFILE; then
all_failed=false all_failed=false
@ -17060,6 +17119,56 @@ determine_optimal_proto() {
"$all_failed" && STARTTLS_OPTIMAL_PROTO="" "$all_failed" && STARTTLS_OPTIMAL_PROTO=""
debugme echo "STARTTLS_OPTIMAL_PROTO: $STARTTLS_OPTIMAL_PROTO" debugme echo "STARTTLS_OPTIMAL_PROTO: $STARTTLS_OPTIMAL_PROTO"
else else
if "$using_sockets"; then
for proto in 03 01 04 00 02 22; do
case $proto in
03) tls_sockets "$proto" "$TLS12_CIPHER"
if [[ $? -eq 0 ]]; then
add_tls_offered tls1_2 yes; OPTIMAL_PROTO="-tls1_2"
all_failed=false
break
elif [[ $? -eq 2 ]]; then
case $(get_protocol "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") in
*1.1) add_tls_offered tls1_1 yes; OPTIMAL_PROTO="-tls1_1" ;;
TLSv1) add_tls_offered tls1 yes; OPTIMAL_PROTO="-tls1" ;;
SSLv3) add_tls_offered ssl3 yes; OPTIMAL_PROTO="-ssl3" ;;
esac
all_failed=false
break
fi ;;
04) tls_sockets "$proto" "$TLS13_CIPHER"
if [[ $? -eq 0 ]]; then
add_tls_offered tls1_3 yes; OPTIMAL_PROTO="-tls1_3"
all_failed=false
break
fi ;;
01|00|02) tls_sockets "$proto" "$TLS_CIPHER" "" "" "true"
if [[ $? -eq 0 ]]; then
case $proto in
01) add_tls_offered tls1 yes; OPTIMAL_PROTO="-tls1" ;;
00) add_tls_offered ssl3 yes; OPTIMAL_PROTO="-ssl3" ;;
02) add_tls_offered tls1_1 yes; OPTIMAL_PROTO="-tls1_1" ;;
esac
all_failed=false
break
elif [[ $? -eq 2 ]]; then
case $(get_protocol "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") in
*1.1) add_tls_offered tls1_1 yes; OPTIMAL_PROTO="-tls1_1" ;;
TLSv1) add_tls_offered tls1 yes; OPTIMAL_PROTO="-tls1" ;;
SSLv3) add_tls_offered ssl3 yes; OPTIMAL_PROTO="-ssl3" ;;
esac
all_failed=false
break
fi ;;
22) sslv2_sockets
[[ $? -eq 0 ]] && all_failed=false && add_tls_offered ssl2 yes && OPTIMAL_PROTO="-ssl2"
;;
esac
done
cp $TEMPDIR/$NODEIP.parse_tls_serverhello.txt $TMPFILE
debugme echo "proto: $proto"
else
# no sockets
for OPTIMAL_PROTO in '' -tls1_2 -tls1 -tls1_3 -ssl3 -tls1_1 -ssl2; do for OPTIMAL_PROTO in '' -tls1_2 -tls1 -tls1_3 -ssl3 -tls1_1 -ssl2; do
case $OPTIMAL_PROTO in case $OPTIMAL_PROTO in
-tls1_3) "$HAS_TLS13" || continue ;; -tls1_3) "$HAS_TLS13" || continue ;;
@ -17077,6 +17186,13 @@ determine_optimal_proto() {
tmp=${tmp/v/} tmp=${tmp/v/}
tmp="$(tolower $tmp)" tmp="$(tolower $tmp)"
add_tls_offered "${tmp}" yes add_tls_offered "${tmp}" yes
case $tmp in
tls1) OPTIMAL_PROTO="-tls1" ;;
tls1_1) OPTIMAL_PROTO="-tls1_1" ;;
tls1_2) OPTIMAL_PROTO="-tls1_2" ;;
tls1_3) OPTIMAL_PROTO="-tls1_3" ;;
ssl2) OPTIMAL_PROTO="-ssl2" ;;
esac
else else
add_tls_offered "${OPTIMAL_PROTO/-/}" yes add_tls_offered "${OPTIMAL_PROTO/-/}" yes
fi fi
@ -17086,6 +17202,7 @@ determine_optimal_proto() {
fi fi
all_failed=true all_failed=true
done done
fi
"$all_failed" && OPTIMAL_PROTO="" "$all_failed" && OPTIMAL_PROTO=""
debugme echo "OPTIMAL_PROTO: $OPTIMAL_PROTO" debugme echo "OPTIMAL_PROTO: $OPTIMAL_PROTO"
if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then
@ -17093,8 +17210,8 @@ determine_optimal_proto() {
ignore_no_or_lame " Type \"yes\" to proceed and accept false negatives or positives" "yes" ignore_no_or_lame " Type \"yes\" to proceed and accept false negatives or positives" "yes"
[[ $? -ne 0 ]] && exit $ERR_CLUELESS [[ $? -ne 0 ]] && exit $ERR_CLUELESS
fi fi
fi
grep -q '^Server Temp Key' $TMPFILE && HAS_DH_BITS=true # FIX #190 grep -q '^Server Temp Key' $TMPFILE && HAS_DH_BITS=true # FIX #190
fi
if "$all_failed"; then if "$all_failed"; then
outln outln
@ -18424,6 +18541,7 @@ nodeip_to_proper_ip6() {
reset_hostdepended_vars() { reset_hostdepended_vars() {
TLS_EXTENSIONS="" TLS_EXTENSIONS=""
PROTOS_OFFERED="" PROTOS_OFFERED=""
CURVES_OFFERED=""
OPTIMAL_PROTO="" OPTIMAL_PROTO=""
SERVER_SIZE_LIMIT_BUG=false SERVER_SIZE_LIMIT_BUG=false
} }