From 9afbba1e04df226a2993c937edb0499c084917f1 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 21 Sep 2016 20:32:04 +0200 Subject: [PATCH 1/3] - 3DES removed from \'MEDIUM\' - preparation to show cipher string in std_cipherlists - global var for HTTP_STATUS_CODE, allowing a hint for web application wrt to e.g. cookies --- testssl.sh | 116 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/testssl.sh b/testssl.sh index d9a1679..a6998a2 100755 --- a/testssl.sh +++ b/testssl.sh @@ -196,6 +196,7 @@ CLIENT_AUTH=false NO_SSL_SESSIONID=false HOSTCERT="" HEADERFILE="" +HTTP_STATUS_CODE="" PROTOS_OFFERED="" TLS_EXTENSIONS="" GOST_STATUS_PROBLEM=false @@ -748,66 +749,66 @@ run_http_header() { mv $HEADERFILE.2 $HEADERFILE # sed'ing in place doesn't work with BSD and Linux simultaneously ret=0 - status_code=$(awk '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) - msg_thereafter=$(awk -F"$status_code" '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) # dirty trick to use the status code as a + HTTP_STATUS_CODE=$(awk '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) + msg_thereafter=$(awk -F"$HTTP_STATUS_CODE" '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) # dirty trick to use the status code as a msg_thereafter=$(strip_lf "$msg_thereafter") # field separator, otherwise we need a loop with awk - debugme echo "Status/MSG: $status_code $msg_thereafter" + debugme echo "Status/MSG: $HTTP_STATUS_CODE $msg_thereafter" pr_bold " HTTP Status Code " - [[ -z "$status_code" ]] && pr_cyan "No status code" && return 3 + [[ -z "$HTTP_STATUS_CODE" ]] && pr_cyan "No status code" && return 3 - out " $status_code$msg_thereafter" - case $status_code in + out " $HTTP_STATUS_CODE$msg_thereafter" + case $HTTP_STATUS_CODE in 301|302|307|308) redirect=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n') out ", redirecting to \"$redirect\"" if [[ $redirect == "http://"* ]]; then pr_svrty_high " -- Redirect to insecure URL (NOT ok)" - fileout "status_code" "NOT ok" \, "Redirect to insecure URL (NOT ok). Url: \"$redirect\"" + fileout "HTTP_STATUS_CODE" "NOT ok" \, "Redirect to insecure URL (NOT ok). Url: \"$redirect\"" fi - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter, redirecting to \"$redirect\"" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter, redirecting to \"$redirect\"" ;; 200) - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter" ;; 204) - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter" ;; 206) out " -- WTF?" - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter -- WTF?" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter -- WTF?" ;; 400) pr_cyan " (Hint: better try another URL)" - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter (Hint: better try another URL)" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter (Hint: better try another URL)" ;; 401) grep -aq "^WWW-Authenticate" $HEADERFILE && out " "; strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)" - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter $(grep -a "^WWW-Authenticate" $HEADERFILE)" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter $(grep -a "^WWW-Authenticate" $HEADERFILE)" ;; 403) - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter" ;; 404) - out " (Hint: supply a path which doesn't give a \"$status_code$msg_thereafter\")" - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter (Hint: supply a path which doesn't give a \"$status_code$msg_thereafter\")" + out " (Hint: supply a path which doesn't give a \"$HTTP_STATUS_CODE$msg_thereafter\")" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter (Hint: supply a path which doesn't give a \"$HTTP_STATUS_CODE$msg_thereafter\")" ;; 405) - fileout "status_code" "INFO" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter" + fileout "HTTP_STATUS_CODE" "INFO" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter" ;; *) - pr_warning ". Oh, didn't expect \"$status_code$msg_thereafter\"" - fileout "status_code" "DEBUG" \ - "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter. Oh, didn't expect a $status_code$msg_thereafter" + pr_warning ". Oh, didn't expect \"$HTTP_STATUS_CODE$msg_thereafter\"" + fileout "HTTP_STATUS_CODE" "DEBUG" \ + "Testing HTTP header response @ \"$URL_PATH\", $HTTP_STATUS_CODE$msg_thereafter. Oh, didn't expect a $HTTP_STATUS_CODE$msg_thereafter" ;; esac outln @@ -1202,16 +1203,28 @@ run_cookie_flags() { # ARG1: Path, ARG2: path local -i nr_cookies local nr_httponly nr_secure local negative_word + local msg302="" msg302_="" if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 fi + + if ! grep -q 20 <<< "$HTTP_STATUS_CODE"; then + if egrep -q "301|302" <<< "$HTTP_STATUS_CODE"; then + msg302=" -- maybe better try target URL of 30x" + msg302_=" (30x detected, better try target URL of 30x)" + else + msg302=" -- HTTP status $HTTP_STATUS_CODE signals you maybe missed the web application" + msg302_=" (maybe missed the application)" + fi + fi + pr_bold " Cookie(s) " grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE if [[ $? -eq 0 ]]; then nr_cookies=$(count_lines "$TMPFILE") - out "$nr_cookies issued: " - fileout "cookie_count" "INFO" "$nr_cookies cookie(s) issued at \"$1\"" + out "$nr_cookies issued:" + fileout "cookie_count" "INFO" "$nr_cookies cookie(s) issued at \"$1\"$msg302_" if [[ $nr_cookies -gt 1 ]]; then negative_word="NONE" else @@ -1235,14 +1248,15 @@ run_cookie_flags() { # ARG1: Path, ARG2: path esac out " HttpOnly" if [[ $nr_cookies == $nr_httponly ]]; then - fileout "cookie_httponly" "OK" "All $nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly" + fileout "cookie_httponly" "OK" "All $nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly$msg302_" else - fileout "cookie_httponly" "WARN" "$nr_secure/$nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly" + fileout "cookie_httponly" "WARN" "$nr_secure/$nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly$msg302_" fi + out "$msg302" else - out "(none issued at \"$1\")" - fileout "cookie_count" "INFO" "No cookies issued at \"$1\"" - fi + out "(none issued at \"$1\")$msg302" + fileout "cookie_count" "INFO" "No cookies issued at \"$1\"$msg302_" + fi outln tmpfile_handle $FUNCNAME.txt @@ -1395,6 +1409,7 @@ listciphers() { # argv[1]: cipher list to test # argv[2]: string on console # argv[3]: ok to offer? 0: yes, 1: no +# argv[4]: string for fileout std_cipherlists() { local -i sclient_success local singlespaces proto="" addcmd="" @@ -1402,8 +1417,8 @@ std_cipherlists() { [[ "$OPTIMAL_PROTO" == "-ssl2" ]] && addcmd="$OPTIMAL_PROTO" && proto="$OPTIMAL_PROTO" [[ ! "$OPTIMAL_PROTO" =~ ssl ]] && addcmd="$SNI" - pr_bold "$2 " # indent in order to be in the same row as server preferences - if listciphers "$1" $proto; then # is that locally available?? + pr_bold "$2 " # indenting to be in the same row as server preferences + if listciphers "$1" $proto; then # is that locally available?? $OPENSSL s_client -cipher "$1" $BUGS $STARTTLS -connect $NODEIP:$PORT $PROXY $addcmd 2>$ERRFILE >$TMPFILE Date: Wed, 21 Sep 2016 21:42:45 +0200 Subject: [PATCH 2/3] - centralized some HAS_* vars from s_client --- testssl.sh | 60 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/testssl.sh b/testssl.sh index a6998a2..216458d 100755 --- a/testssl.sh +++ b/testssl.sh @@ -218,6 +218,9 @@ HAS_SSL3=false HAS_NO_SSL2=false HAS_ALPN=false HAS_SPDY=false +HAS_FALLBACK_SCSV=false +HAS_PROXY=false +HAS_XMPP=false ADD_RFC_STR="rfc" # display RFC ciphernames PORT=443 # unless otherwise auto-determined, see below NODE="" @@ -1800,7 +1803,8 @@ run_cipher_per_proto() { has_server_protocol "${proto:1}" || continue # The OpenSSL ciphers function, prior to version 1.1.0, could only understand -ssl2, -ssl3, and -tls1. - if [[ "$proto" == "-ssl2" ]] || [[ "$proto" == "-ssl3" ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == "1.1.0"* ]]; then + if [[ "$proto" == "-ssl2" ]] || [[ "$proto" == "-ssl3" ]] || \ + [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == "1.1.0"* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == "1.1.1"* ]]; then ossl_ciphers_proto="$proto" else ossl_ciphers_proto="-tls1" @@ -3823,7 +3827,9 @@ determine_trust() { # and the output should should be indented by two more spaces. [[ -n $json_prefix ]] && spaces=" " - if [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.0.2" ]] && [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.1.0" ]]; then + if [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.0.2" ]] && \ + [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.1.0" ]] && \ + [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.1.1" ]]; then addtl_warning="(Your openssl <= 1.0.2 might be too unreliable to determine trust)" fileout "${json_prefix}chain_of_trust_warn" "WARN" "$addtl_warning" fi @@ -6499,8 +6505,7 @@ run_crime() { # return $ret # esac -# $OPENSSL s_client -help 2>&1 | grep -qw nextprotoneg -# if [[ $? -eq 0 ]]; then +# if "$HAS_NPN"; then # $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs $SNI /dev/null >$TMPFILE # if [[ $? -eq 0 ]]; then # echo @@ -6629,7 +6634,7 @@ run_tls_fallback_scsv() { # the countermeasure to protect against protocol downgrade attacks. # First check we have support for TLS_FALLBACK_SCSV in our local OpenSSL - if ! $OPENSSL s_client -help 2>&1 | grep -q "\-fallback_scsv"; then + if ! "$HAS_FALLBACK_SCSV"; then local_problem_ln "$OPENSSL lacks TLS_FALLBACK_SCSV support" return 4 fi @@ -7195,6 +7200,8 @@ test_openssl_suffix() { find_openssl_binary() { + local s_client_has=$TEMPDIR/s_client_has.txt + # 0. check environment variable whether it's executable if [[ -n "$OPENSSL" ]] && [[ ! -x "$OPENSSL" ]]; then pr_warningln "\ncannot find specified (\$OPENSSL=$OPENSSL) binary." @@ -7226,7 +7233,7 @@ find_openssl_binary() { # see #190, reverting logic: unless otherwise proved openssl has no dh bits case "$OSSL_VER_MAJOR.$OSSL_VER_MINOR" in - 1.0.2|1.1.0) HAS_DH_BITS=true ;; + 1.0.2|1.1.0|1.1.1) HAS_DH_BITS=true ;; esac # libressl does not have "Server Temp Key" (SSL_get_server_tmp_key) @@ -7235,6 +7242,8 @@ find_openssl_binary() { pr_warning "Please note: LibreSSL is not a good choice for testing INSECURE features!" fi + initialize_engine + OPENSSL_NR_CIPHERS=$(count_ciphers "$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>/dev/null)") $OPENSSL s_client -ssl2 2>&1 | grep -aq "unknown option" || \ @@ -7246,12 +7255,23 @@ find_openssl_binary() { $OPENSSL s_client -no_ssl2 2>&1 | grep -aq "unknown option" || \ HAS_NO_SSL2=true - $OPENSSL s_client -help 2>&1 | grep -qw '\-alpn' && \ + $OPENSSL s_client -help 2>$s_client_has + + grep -qw '\-alpn' $s_client_has && \ HAS_ALPN=true - $OPENSSL s_client -help 2>&1 | grep -qw '\-nextprotoneg' && \ + grep -qw '\-nextprotoneg' $s_client_has && \ HAS_SPDY=true + grep -qw '\-fallback_scsv' $s_client_has && \ + HAS_FALLBACK_SCSV=true + + grep -q '\-proxy' $s_client_has && \ + HAS_PROXY=true + + grep -q '\-xmpp' $s_client_has && \ + HAS_XMPP=true + return 0 } @@ -7401,10 +7421,13 @@ maketempf() { ERRFILE=$TEMPDIR/errorfile.txt || exit -6 fi HOSTCERT=$TEMPDIR/host_certificate.txt - initialize_engine +} + +prepare_debug() { if [[ $DEBUG -ne 0 ]]; then cat >$TEMPDIR/environment.txt << EOF + CVS_REL: $CVS_REL GIT_REL: $GIT_REL @@ -7432,6 +7455,9 @@ HAS_SSL3: $HAS_SSL3 HAS_NO_SSL2: $HAS_NO_SSL2 HAS_SPDY: $HAS_SPDY HAS_ALPN: $HAS_ALPN +HAS_FALLBACK_SCSV: $HAS_FALLBACK_SCSV +HAS_PROXY: $HAS_PROXY +HAS_XMPP: $HAS_XMPP PATH: $PATH PROG_NAME: $PROG_NAME @@ -7468,11 +7494,11 @@ CCS_MAX_WAITSOCK: $CCS_MAX_WAITSOCK USLEEP_SND $USLEEP_SND USLEEP_REC $USLEEP_REC - EOF which locale &>/dev/null && locale >>$TEMPDIR/environment.txt || echo "locale doesn't exist" >>$TEMPDIR/environment.txt $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' &>$TEMPDIR/all_local_ciphers.txt fi + # see also $TEMPDIR/s_client_has.txt from find_openssl_binary } @@ -7920,8 +7946,8 @@ get_mx_record() { # check_proxy() { if [[ -n "$PROXY" ]]; then - if ! $OPENSSL s_client -help 2>&1 | grep -qw proxy; then - fatal "Your $OPENSSL is too old to support the \"--proxy\" option" -5 + if ! "$HAS_PROXY"; then + fatal "Your $OPENSSL is too old to support the \"-proxy\" option" -5 fi PROXYNODE=${PROXY%:*} PROXYPORT=${PROXY#*:} @@ -8064,7 +8090,7 @@ determine_service() { # for XMPP, openssl has a problem using -connect $NODEIP:$PORT. thus we use -connect $NODE:$PORT instead! NODEIP="$NODE" if [[ -n "$XMPP_HOST" ]]; then - if ! $OPENSSL s_client --help 2>&1 | grep -q xmpphost; then + if ! "$HAS_XMPP"; then fatal "Your $OPENSSL does not support the \"-xmpphost\" option" -5 fi STARTTLS="$STARTTLS -xmpphost $XMPP_HOST" # it's a hack -- instead of changing calls all over the place @@ -8350,8 +8376,9 @@ parse_cmd_line() { help 0 ;; -b|--banner|-v|--version) - find_openssl_binary maketempf + find_openssl_binary + prepare_debug mybanner exit 0 ;; @@ -8754,8 +8781,9 @@ get_install_dir initialize_globals parse_cmd_line "$@" set_color_functions -find_openssl_binary maketempf +find_openssl_binary +prepare_debug mybanner check_proxy check4openssl_oldfarts @@ -8817,4 +8845,4 @@ fi exit $? -# $Id: testssl.sh,v 1.543 2016/09/10 17:37:58 dirkw Exp $ +# $Id: testssl.sh,v 1.545 2016/09/21 19:42:44 dirkw Exp $ From ddbf4caa463295552c35b24d2a0871c40cb1caee Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 21 Sep 2016 21:59:50 +0200 Subject: [PATCH 3/3] FIX #476 --- testssl.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/testssl.sh b/testssl.sh index 216458d..7df6836 100755 --- a/testssl.sh +++ b/testssl.sh @@ -7787,6 +7787,7 @@ check_resolver_bins() { # arg1: a host name. Returned will be 0-n IPv4 addresses get_a_record() { local ip4="" + local cname_temp="" local saved_openssl_conf="$OPENSSL_CONF" OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 @@ -7800,8 +7801,14 @@ get_a_record() { fi fi if [[ -z "$ip4" ]]; then - which dig &> /dev/null && \ - ip4=$(filter_ip4_address $(dig +short -t a "$1" 2>/dev/null | sed '/^;;/d')) + if which dig &> /dev/null ; then + cname_temp=$(dig +short -t CNAME "$1" 2>/dev/null) + if [[ -n "$cname_temp" ]]; then + ip4=$(filter_ip4_address $(dig +short -t a "$cname_temp" 2>/dev/null | sed '/^;;/d')) + else + ip4=$(filter_ip4_address $(dig +short -t a "$1" 2>/dev/null | sed '/^;;/d')) + fi + fi fi if [[ -z "$ip4" ]]; then which host &> /dev/null && \ @@ -8845,4 +8852,4 @@ fi exit $? -# $Id: testssl.sh,v 1.545 2016/09/21 19:42:44 dirkw Exp $ +# $Id: testssl.sh,v 1.546 2016/09/21 19:59:48 dirkw Exp $