From af5cad91837221abd67988b77c45416519d24dda Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 24 Aug 2020 16:22:04 +0200 Subject: [PATCH 01/14] Additions to find_openssl_binary() for a new openssl version / cleanup() This is a small cleanup of find_openssl_binary(). It tries also to find a newer openssl version which we could need for any new features. As stated in the comment at some point we should decide whether we stick with our old version or rather supply a new one. (xmpp-server is also not builtin for our 1.0.2) or maybe find a good way (code) how to use both. Also it looks for socat and if found it populates the according global var. It does a minor resort of global vars in the beginning. --- Dockerfile | 2 +- testssl.sh | 152 ++++++++++++++++++++++++++++------------------------- 2 files changed, 82 insertions(+), 72 deletions(-) diff --git a/Dockerfile b/Dockerfile index 42a6941..4bb321f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM alpine:3.11 RUN apk update && \ apk upgrade && \ - apk add --no-cache bash procps drill git coreutils libidn curl && \ + apk add --no-cache bash procps drill git coreutils libidn curl socat && \ addgroup testssl && \ adduser -G testssl -g "testssl user" -s /bin/bash -D testssl && \ ln -s /home/testssl/testssl.sh /usr/local/bin/ && \ diff --git a/testssl.sh b/testssl.sh index ed93ca8..997204d 100755 --- a/testssl.sh +++ b/testssl.sh @@ -217,11 +217,8 @@ UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) NO_ENGINE=${NO_ENGINE:-false} # if there are problems finding the (external) openssl engine set this to true declare -r CLIENT_MIN_FS=5 # number of ciphers needed to run a test for FS CAPATH="${CAPATH:-/etc/ssl/certs/}" # Does nothing yet (FC has only a CA bundle per default, ==> openssl version -d) -GOOD_CA_BUNDLE="" # A bundle of CA certificates that can be used to validate the server's certificate -CERTIFICATE_LIST_ORDERING_PROBLEM=false # Set to true if server sends a certificate list that contains a certificate - # that does not certify the one immediately preceding it. (See RFC 8446, Section 4.4.2) -STAPLED_OCSP_RESPONSE="" -HAS_DNS_SANS=false # Whether the certificate includes a subjectAltName extension with a DNS name or an application-specific identifier type. +SOCAT="${SOCAT:-}" # For now we would need this for STARTTLS injection + MEASURE_TIME_FILE=${MEASURE_TIME_FILE:-""} if [[ -n "$MEASURE_TIME_FILE" ]] && [[ -z "$MEASURE_TIME" ]]; then MEASURE_TIME=true @@ -238,6 +235,8 @@ SYSTEM2="" # currently only being used for WSL = ba PRINTF="" # which external printf to use. Empty presets the internal one, see #1130 CIPHERS_BY_STRENGTH_FILE="" TLS_DATA_FILE="" # mandatory file for socket-based handshakes +OPENSSL="" # If you run this from github it's ~/bin/openssl.$(uname).$(uname -m) otherwise /usr/bin/openssl +OPENSSL2="" # When running from github, this will be openssl version >=1.1.1 (auto determined) OPENSSL_LOCATION="" IKNOW_FNAME=false FIRST_FINDING=true # is this the first finding we are outputting to file? @@ -294,13 +293,21 @@ NW_STR="" LEN_STR="" SNI="" POODLE="" # keep vulnerability status for TLS_FALLBACK_SCSV + +# Initialize OpenSSL variables (and others) OSSL_NAME="" # openssl name, in case of LibreSSL it's LibreSSL OSSL_VER="" # openssl version, will be auto-determined OSSL_VER_MAJOR=0 OSSL_VER_MINOR=0 OSSL_VER_APPENDIX="none" CLIENT_PROB_NO=1 -HAS_DH_BITS=${HAS_DH_BITS:-false} # initialize openssl variables + +GOOD_CA_BUNDLE="" # A bundle of CA certificates that can be used to validate the server's certificate +CERTIFICATE_LIST_ORDERING_PROBLEM=false # Set to true if server sends a certificate list that contains a certificate + # that does not certify the one immediately preceding it. (See RFC 8446, Section 4.4.2) +STAPLED_OCSP_RESPONSE="" +HAS_DNS_SANS=false # Whether the certificate includes a subjectAltName extension with a DNS name or an application-specific identifier type. +HAS_DH_BITS=${HAS_DH_BITS:-false} # These are variables which are set by find_openssl_binary() HAS_CURVES=false OSSL_SUPPORTED_CURVES="" HAS_SSL2=false @@ -332,6 +339,8 @@ HAS_CHACHA20=false HAS_AES128_GCM=false HAS_AES256_GCM=false HAS_ZLIB=false +HAS_UDS=false +HAS_UDS2=false HAS_DIG=false HAS_HOST=false HAS_DRILL=false @@ -18491,7 +18500,9 @@ test_openssl_suffix() { find_openssl_binary() { local s_client_has=$TEMPDIR/s_client_has.txt + local s_client_has2=$TEMPDIR/s_client_has2.txt local s_client_starttls_has=$TEMPDIR/s_client_starttls_has.txt + local s_client_starttls_has2=$TEMPDIR/s_client_starttls_has2 local openssl_location cwd="" local ossl_wo_dev_info local curve @@ -18581,6 +18592,7 @@ find_openssl_binary() { HAS_PROXY=false HAS_XMPP=false HAS_XMPP_SERVER=false + HAS_XMPP_SERVER2=false HAS_POSTGRES=false HAS_MYSQL=false HAS_LMTP=false @@ -18590,48 +18602,34 @@ find_openssl_binary() { HAS_AES128_GCM=false HAS_AES256_GCM=false HAS_ZLIB=false + HAS_UDS=false + HAS_UDS2=false - $OPENSSL ciphers -s 2>&1 | grep -aiq "unknown option" || \ - OSSL_CIPHERS_S="-s" + $OPENSSL ciphers -s 2>&1 | grep -aiq "unknown option" || OSSL_CIPHERS_S="-s" # This and all other occurences we do a little trick using "invalid." to avoid plain and # link level DNS lookups. See issue #1418 and https://tools.ietf.org/html/rfc6761#section-6.4 - $OPENSSL s_client -ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_SSL2=true + $OPENSSL s_client -ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_SSL2=true + $OPENSSL s_client -ssl3 -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_SSL3=true + $OPENSSL s_client -tls1_3 -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_TLS13=true + $OPENSSL s_client -no_ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_NO_SSL2=true - $OPENSSL s_client -ssl3 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_SSL3=true - - $OPENSSL s_client -tls1_3 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_TLS13=true - - $OPENSSL genpkey -algorithm X448 2>&1 | grep -aq "not found" || \ - HAS_X448=true - - $OPENSSL genpkey -algorithm X25519 2>&1 | grep -aq "not found" || \ - HAS_X25519=true + $OPENSSL genpkey -algorithm X448 2>&1 | grep -aq "not found" || HAS_X448=true + $OPENSSL genpkey -algorithm X25519 2>&1 | grep -aq "not found" || HAS_X25519=true + $OPENSSL pkey -help 2>&1 | grep -q Error || HAS_PKEY=true + $OPENSSL pkeyutl 2>&1 | grep -q Error || HAS_PKUTIL=true if "$HAS_TLS13"; then - $OPENSSL s_client -tls1_3 -sigalgs PSS+SHA256:PSS+SHA384 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_SIGALGS=true + $OPENSSL s_client -tls1_3 -sigalgs PSS+SHA256:PSS+SHA384 -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_SIGALGS=true fi - $OPENSSL s_client -no_ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_NO_SSL2=true - $OPENSSL s_client -noservername -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_NOSERVERNAME=true + $OPENSSL s_client -noservername -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_NOSERVERNAME=true + $OPENSSL s_client -ciphersuites -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_CIPHERSUITES=true - $OPENSSL s_client -ciphersuites -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_CIPHERSUITES=true + $OPENSSL ciphers @SECLEVEL=0:ALL > /dev/null 2> /dev/null && HAS_SECLEVEL=true - $OPENSSL ciphers @SECLEVEL=0:ALL > /dev/null 2> /dev/null - [[ $? -eq 0 ]] && HAS_SECLEVEL=true - - $OPENSSL s_client -comp -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_COMP=true - - $OPENSSL s_client -no_comp -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_NO_COMP=true + $OPENSSL s_client -comp -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_COMP=true + $OPENSSL s_client -no_comp -connect invalid. 2>&1 | grep -aiq "unknown option" || HAS_NO_COMP=true OPENSSL_NR_CIPHERS=$(count_ciphers "$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL')") @@ -18648,50 +18646,40 @@ find_openssl_binary() { done fi - $OPENSSL pkey -help 2>&1 | grep -q Error || \ - HAS_PKEY=true - - $OPENSSL pkeyutl 2>&1 | grep -q Error || \ - HAS_PKUTIL=true - # For the following we feel safe enough to query the s_client help functions. # That was not good enough for the previous lookups $OPENSSL s_client -help 2>$s_client_has - $OPENSSL s_client -starttls foo 2>$s_client_starttls_has - grep -qw '\-alpn' $s_client_has && \ - HAS_ALPN=true + grep -q '\-proxy' $s_client_has && HAS_PROXY=true + grep -qw '\-alpn' $s_client_has && HAS_ALPN=true + grep -qw '\-nextprotoneg' $s_client_has && HAS_NPN=true - grep -qw '\-nextprotoneg' $s_client_has && \ - HAS_NPN=true + grep -qw '\-fallback_scsv' $s_client_has && HAS_FALLBACK_SCSV=true - grep -qw '\-fallback_scsv' $s_client_has && \ - HAS_FALLBACK_SCSV=true + grep -q 'xmpp' $s_client_starttls_has && HAS_XMPP=true + grep -q 'xmpp-server' $s_client_starttls_has && HAS_XMPP_SERVER=true - grep -q '\-proxy' $s_client_has && \ - HAS_PROXY=true + grep -q 'postgres' $s_client_starttls_has && HAS_POSTGRES=true + grep -q 'mysql' $s_client_starttls_has && HAS_MYSQL=true + grep -q 'lmtp' $s_client_starttls_has && HAS_LMTP=true + grep -q 'nntp' $s_client_starttls_has && HAS_NNTP=true + grep -q 'irc' $s_client_starttls_has && HAS_IRC=true - grep -q 'xmpp' $s_client_starttls_has && \ - HAS_XMPP=true + grep -q 'Unix-domain socket' $s_client_has && HAS_UDS=true - grep -q 'xmpp-server' $s_client_starttls_has && \ - HAS_XMPP_SERVER=true - - grep -q 'postgres' $s_client_starttls_has && \ - HAS_POSTGRES=true - - grep -q 'mysql' $s_client_starttls_has && \ - HAS_MYSQL=true - - grep -q 'lmtp' $s_client_starttls_has && \ - HAS_LMTP=true - - grep -q 'nntp' $s_client_starttls_has && \ - HAS_NNTP=true - - grep -q 'irc' $s_client_starttls_has && \ - HAS_IRC=true + # Now check whether the standard $OPENSSL has Unix-domain socket and xmpp-server support. If + # not check /usr/bin/openssl -- if available. This is more a kludge which we shouldn't use for + # every openssl feature. At some point we need to decide which with openssl version we go. + OPENSSL2=/usr/bin/openssl + if [[ ! $OSSL_VER =~ 1.1.1 ]] && [[ ! $OSSL_VER_MAJOR =~ 3 ]]; then + if [[ -x $OPENSSL2 ]]; then + $OPENSSL2 s_client -help 2>$s_client_has2 + $OPENSSL2 s_client -starttls foo 2>$s_client_starttls_has2 + grep -q 'Unix-domain socket' $s_client_has2 && HAS_UDS2=true + grep -q 'xmpp-server' $s_client_starttls_has2 && HAS_XMPP_SERVER2=true + fi + fi $OPENSSL enc -chacha20 -K 12345678901234567890123456789012 -iv 01000000123456789012345678901234 > /dev/null 2> /dev/null <<< "test" [[ $? -eq 0 ]] && HAS_CHACHA20=true @@ -18732,6 +18720,21 @@ find_openssl_binary() { } +find_socat() { + local result"" + + result=$(type -p socat) + if [[ $? -ne 0 ]]; then + return 1 + else + if [[ -x $result ]] && $result -V | grep -iaq 'socat version' ; then + SOCAT=$result + return 0 + fi + fi +} + + check4openssl_oldfarts() { case "$OSSL_VER" in 0.9.7*|0.9.6*|0.9.5*) @@ -19000,11 +19003,14 @@ HAS_PKUTIL: $HAS_PKUTIL HAS_PROXY: $HAS_PROXY HAS_XMPP: $HAS_XMPP HAS_XMPP_SERVER: $HAS_XMPP_SERVER +HAS_XMPP_SERVER2: $HAS_XMPP_SERVER2 HAS_POSTGRES: $HAS_POSTGRES HAS_MYSQL: $HAS_MYSQL HAS_LMTP: $HAS_LMTP HAS_NNTP: $HAS_NNTP HAS_IRC: $HAS_IRC +HAS_UDS: $HAS_UDS +HAS_UDS2: $HAS_UDS2 HAS_DIG: $HAS_DIG HAS_HOST: $HAS_HOST @@ -19054,6 +19060,8 @@ CCS_MAX_WAITSOCK: $CCS_MAX_WAITSOCK USLEEP_SND $USLEEP_SND USLEEP_REC $USLEEP_REC +SOCAT: $SOCAT + EOF type -p locale &>/dev/null && locale >>$TEMPDIR/environment.txt || echo "locale doesn't exist" >>$TEMPDIR/environment.txt actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL' "-V" &>$TEMPDIR/all_local_ciphers.txt @@ -20216,6 +20224,7 @@ determine_service() { fi fi if [[ "$protocol" == xmpp-server ]] && ! "$HAS_XMPP_SERVER"; then + #FIXME: make use of HAS_XMPP_SERVER2 fatal "Your $OPENSSL does not support the \"-starttls xmpp-server\" option" $ERR_OSSLBIN fi elif [[ "$protocol" == postgres ]]; then @@ -22076,6 +22085,7 @@ lets_roll() { set_color_functions maketempf find_openssl_binary + find_socat choose_printf check_resolver_bins prepare_debug ; stopwatch parse From 4f8fe42f0cb4a66af58cf85041724e8618321db8 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Thu, 27 Aug 2020 23:00:50 +0200 Subject: [PATCH 02/14] Prepared smtp/lmtp to prepare for addition commands after STARTTLS --- testssl.sh | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/testssl.sh b/testssl.sh index 997204d..9179f01 100755 --- a/testssl.sh +++ b/testssl.sh @@ -10431,7 +10431,8 @@ starttls_just_send(){ local -i ret=0 debugme echo "C: $1\r\n" - echo -ne "$1\r\n" >&5 + # We need cat here, otherwise the appended ELHO after STARTTLS will be in the next packet + printf "%b" "$1\r\n" | cat >&5 ret=$? if [[ $ret -eq 0 ]]; then debugme echo " > succeeded: $2" @@ -10536,24 +10537,24 @@ starttls_smtp_dialog() { local greet_str="EHLO testssl.sh" local proto="smtp" local reSTARTTLS='^250[ -]STARTTLS' + local starttls="STARTTLS" local -i ret=0 + "$SNEAKY" && greet_str="EHLO google.com" + if [[ -n "$2" ]]; then + # Here we can supply and addtional command after STARTTLS for injection tests + starttls="STARTTLS\r\n$2" + fi if [[ "$1" == lmtp ]]; then proto="lmtp" greet_str="LHLO" fi - if [[ -n "$2" ]]; then - # Here we can "add" commands in the future. Please note \r\n will automatically be appended - greet_str="$2" - elif "$SNEAKY"; then - greet_str="EHLO google.com" - fi debugme echo "=== starting $proto STARTTLS dialog ===" starttls_full_read '^220-' '^220 ' '' "received server greeting" && starttls_just_send "$greet_str" "sent $greet_str" && starttls_full_read '^250-' '^250 ' "${reSTARTTLS}" "received server capabilities and checked STARTTLS availability" && - starttls_just_send 'STARTTLS' "initiated STARTTLS" && + starttls_just_send "$starttls" "initiated STARTTLS" && starttls_full_read '^220-' '^220 ' '' "received ack for STARTTLS" ret=$? debugme echo "=== finished $proto STARTTLS dialog with ${ret} ===" @@ -10562,10 +10563,11 @@ starttls_smtp_dialog() { starttls_pop3_dialog() { local -i ret=0 + local starttls="STLS" debugme echo "=== starting pop3 STARTTLS dialog ===" starttls_full_read '^\+OK' '^\+OK' '' "received server greeting" && - starttls_just_send 'STLS' "initiated STARTTLS" && + starttls_just_send "$starttls" "initiated STARTTLS" && starttls_full_read '^\+OK' '^\+OK' '' "received ack for STARTTLS" ret=$? debugme echo "=== finished pop3 STARTTLS dialog with ${ret} ===" @@ -10575,12 +10577,13 @@ starttls_pop3_dialog() { starttls_imap_dialog() { local -i ret=0 local reSTARTTLS='^\* CAPABILITY(( .*)? IMAP4rev1( .*)? STARTTLS(.*)?|( .*)? STARTTLS( .*)? IMAP4rev1(.*)?)$' + local starttls="a002 STARTTLS" debugme echo "=== starting imap STARTTLS dialog ===" starttls_full_read '^\* ' '^\* OK ' '' "received server greeting" && starttls_just_send 'a001 CAPABILITY' "sent CAPABILITY" && starttls_full_read '^\* ' '^a001 OK ' "${reSTARTTLS}" "received server capabilities and checked STARTTLS availability" && - starttls_just_send 'a002 STARTTLS' "initiated STARTTLS" && + starttls_just_send "$starttls" "initiated STARTTLS" && starttls_full_read '^\* ' '^a002 OK ' '' "received ack for STARTTLS" ret=$? debugme echo "=== finished imap STARTTLS dialog with ${ret} ===" From 6c966a5a7f4a62834fa9fb4308b9eb6c1a2036f0 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 28 Aug 2020 00:50:06 +0200 Subject: [PATCH 03/14] Implementation of STARTTLS injection fo smtp It's more a PoC style and needs some work * use $OPENSSL or $OPENSSL2 * remove exit 0 * put run_starttls_injection below run_rc4 * test with more vulnerable servers debugme1() was defined --- testssl.sh | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9179f01..f1da440 100755 --- a/testssl.sh +++ b/testssl.sh @@ -768,6 +768,7 @@ debugme() { [[ "$DEBUG" -ge 2 ]] && "$@" return 0 } +debugme1() { [[ "$DEBUG" -ge 2 ]] && "$@"; } hex2dec() { echo $((16#$1)) @@ -10659,10 +10660,13 @@ starttls_mysql_dialog() { return $ret } -# arg1: fd for socket -- which we don't use as it is a hassle and it is not clear whether it works under every bash version +# arg1: fd for socket -- which we don't use yes as it is a hassle (not clear whether it works under every bash version) +# arg2: optional: for STARTTLS additional command to be injected # returns 6 if opening the socket caused a problem, 1 if STARTTLS handshake failed, 0: all ok # fd_socket() { + local fd="$1" + local payload="$2" local proyxline="" local nodeip="$(tr -d '[]' <<< $NODEIP)" # sockets do not need the square brackets we have of IPv6 addresses # we just need do it here, that's all! @@ -10726,7 +10730,7 @@ fd_socket() { starttls_ftp_dialog ;; smtp|smtps) # SMTP, see https://tools.ietf.org/html/rfc{2033,3207,5321} - starttls_smtp_dialog + starttls_smtp_dialog "" "$payload" ;; lmtp|lmtps) # LMTP, see https://tools.ietf.org/html/rfc{2033,3207,5321} starttls_smtp_dialog lmtp @@ -17783,6 +17787,62 @@ run_tls_truncation() { : } + +run_starttls_injection() { + local cve="" + local cwe="CWE-74" + local hint="" + local jsonID="starttls_injection" + local uds="" + + [[ -z "$STARTTLS" ]] && return 0 + + if [[ -z "$SOCAT" ]]; then + fileout "$jsonID" "WARN" "Need socat for this" "$cve" "$cwe" "$hint" + debugme1 echo "Need socat for this check" + return 1 + fi + if [[ -z "$HAS_UDS2" ]] && [[ -z "$HAS_UDS" ]]; then + fileout "$jsonID" "WARN" "Need OpenSSL with Unix-domain socket s_client support for this check" "$cve" "$cwe" "$hint" + debugme1 echo "Need an OpenSSL with Unix-domain socket s_client support for this check" + return 1 + fi + if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then + outln + pr_headlineln " Checking for STARTTLS injection " + outln + fi + pr_bold " STARTTLS injection" ; out " " + + uds=$TEMPDIR/uds + + fd_socket 5 "EHLO google.com" + socat FD:5 UNIX-LISTEN:$uds & + # normally the interesting fallback is in fd2: + openssl s_client -unix $uds >$TMPFILE 2>&1 & +# FIXME: should be some OPENSSL + sleep 1 + [[ "$DEBUG" -ge 4 ]] && cat $TMPFILE + if grep -Eqa '^250-|^503 ' $TMPFILE; then + out "likely " + prln_svrty_high "VULNERABLE (NOT ok)" + fileout "$jsonID" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint" + else + prln_svrty_good "not vulnerable (OK)" + fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" + fi + outln + +exit 0 + + outln "\n" + tmpfile_handle ${FUNCNAME[0]}.txt + return 0 + + +} + + # Test for various server implementation errors that aren't tested for elsewhere. # Inspired by RFC 8701. run_grease() { @@ -21180,6 +21240,7 @@ initialize_globals() { do_fs=false do_protocols=false do_rc4=false + do_starttls_injection=false do_grease=false do_renego=false do_cipherlists=false @@ -21217,6 +21278,7 @@ set_scanning_defaults() { do_header=true do_fs=true do_rc4=true + do_starttls_injection=true do_protocols=true do_renego=true do_cipherlists=true @@ -21238,7 +21300,7 @@ count_do_variables() { local true_nr=0 for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ - do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \ + do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_starttls_injection do_grease do_robot do_renego \ do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_rating; do "${!gbl}" && let true_nr++ @@ -21251,7 +21313,7 @@ debug_globals() { local gbl for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ - do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \ + do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_starttls_injection do_grease do_robot do_renego \ do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_rating; do printf "%-22s = %s\n" $gbl "${!gbl}" @@ -21459,6 +21521,7 @@ parse_cmd_line() { do_beast=true do_lucky13=true do_rc4=true + do_starttls_injection=true if "$OFFENSIVE"; then VULN_COUNT=16 else @@ -21862,6 +21925,10 @@ parse_cmd_line() { grep -q "BEGIN CERTIFICATE" "$fname" || fatal "\"$fname\" is not CA file in PEM format" $ERR_RESOURCE done + if "$do_starttls_injection" && [[ "$STARTTLS_PROTOCOL" =~ smtp ]]; then + let "VULN_COUNT++" + fi + count_do_variables [[ $? -eq 0 ]] && set_scanning_defaults set_skip_tests @@ -22016,6 +22083,9 @@ lets_roll() { fi fileout_section_header $section_number true && ((section_number++)) + + "$do_starttls_injection" && { run_starttls_injection; ret=$(($? + ret)); stopwatch run_starttls_injection; } + "$do_heartbleed" && { run_heartbleed; ret=$(($? + ret)); stopwatch run_heartbleed; } "$do_ccs_injection" && { run_ccs_injection; ret=$(($? + ret)); stopwatch run_ccs_injection; } "$do_ticketbleed" && { run_ticketbleed; ret=$(($? + ret)); stopwatch run_ticketbleed; } From 32b5219206ca255cccb66deefca8cce8ddc26dca Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 28 Aug 2020 18:25:51 +0200 Subject: [PATCH 04/14] Finalized SMTP * addressed open issues from previous commit * defined a cmd line switch ToDos: * help() * POP * IMAP --- testssl.sh | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/testssl.sh b/testssl.sh index f1da440..10a0c46 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17761,7 +17761,6 @@ run_rc4() { prln_svrty_good "no RC4 ciphers detected (OK)" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" fi - outln "$using_sockets" && HAS_DH_BITS="$has_dh_bits" tmpfile_handle ${FUNCNAME[0]}.txt @@ -17789,11 +17788,13 @@ run_tls_truncation() { run_starttls_injection() { + local uds="" + local openssl_bin="" + local -i socat_pid=424242 local cve="" local cwe="CWE-74" local hint="" local jsonID="starttls_injection" - local uds="" [[ -z "$STARTTLS" ]] && return 0 @@ -17812,16 +17813,23 @@ run_starttls_injection() { pr_headlineln " Checking for STARTTLS injection " outln fi - pr_bold " STARTTLS injection" ; out " " + pr_bold " STARTTLS injection" ; out " (experimental) " uds=$TEMPDIR/uds fd_socket 5 "EHLO google.com" - socat FD:5 UNIX-LISTEN:$uds & - # normally the interesting fallback is in fd2: - openssl s_client -unix $uds >$TMPFILE 2>&1 & -# FIXME: should be some OPENSSL + $SOCAT FD:5 UNIX-LISTEN:$uds & + socat_pid=$! + + if "$HAS_UDS"; then + openssl_bin=$OPENSSL + else + openssl_bin=$OPENSSL2 + fi + # normally the interesting fallback we grep later for is in fd2 but we'll catch all here + $openssl_bin s_client -unix $uds >$TMPFILE 2>&1 & sleep 1 + kill $socat_pid [[ "$DEBUG" -ge 4 ]] && cat $TMPFILE if grep -Eqa '^250-|^503 ' $TMPFILE; then out "likely " @@ -17831,15 +17839,9 @@ run_starttls_injection() { prln_svrty_good "not vulnerable (OK)" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" fi - outln - -exit 0 - - outln "\n" tmpfile_handle ${FUNCNAME[0]}.txt + return 0 - - } @@ -21595,6 +21597,10 @@ parse_cmd_line() { do_rc4=true let "VULN_COUNT++" ;; + -SI|--SI|--starttls[-_]injection) + do_starttls_injection=true + let "VULN_COUNT++" + ;; -f|--fs|--nsa|--forward-secrecy) do_fs=true ;; @@ -22084,8 +22090,6 @@ lets_roll() { fileout_section_header $section_number true && ((section_number++)) - "$do_starttls_injection" && { run_starttls_injection; ret=$(($? + ret)); stopwatch run_starttls_injection; } - "$do_heartbleed" && { run_heartbleed; ret=$(($? + ret)); stopwatch run_heartbleed; } "$do_ccs_injection" && { run_ccs_injection; ret=$(($? + ret)); stopwatch run_ccs_injection; } "$do_ticketbleed" && { run_ticketbleed; ret=$(($? + ret)); stopwatch run_ticketbleed; } @@ -22102,6 +22106,8 @@ lets_roll() { "$do_beast" && { run_beast; ret=$(($? + ret)); stopwatch run_beast; } "$do_lucky13" && { run_lucky13; ret=$(($? + ret)); stopwatch run_lucky13; } "$do_rc4" && { run_rc4; ret=$(($? + ret)); stopwatch run_rc4; } + "$do_starttls_injection" && { run_starttls_injection; ret=$(($? + ret)); stopwatch run_starttls_injection; } + outln fileout_section_header $section_number true && ((section_number++)) "$do_allciphers" && { run_allciphers; ret=$(($? + ret)); stopwatch run_allciphers; } From 5560e17b01b37427121ebb6b681267871633fe20 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Sat, 29 Aug 2020 09:17:17 +0200 Subject: [PATCH 05/14] Cleanup stuff in run_starttls_injection() and more run_starttls_injection(): * kill background openssl process when not needed anymore * kill background socat process when not needed anymore * close socket add line in help() for STARTTLS injection Furthermore: * for close_socket() calls always add the fd (atm not needed) * in help() rather advertise --BB instead of -BB --- testssl.sh | 53 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/testssl.sh b/testssl.sh index 10a0c46..5545f6a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -4638,7 +4638,7 @@ client_simulation_sockets() { tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then - close_socket + close_socket 5 TMPFILE=$SOCK_REPLY_FILE tmpfile_handle ${FUNCNAME[0]}.dd return $ret @@ -4720,7 +4720,7 @@ client_simulation_sockets() { debugme tmln_out fi - close_socket + close_socket 5 TMPFILE=$SOCK_REPLY_FILE tmpfile_handle ${FUNCNAME[0]}.dd return $ret @@ -10690,14 +10690,14 @@ fd_socket() { read -t $PROXY_WAIT -r proyxline <&5 if [[ $? -ge 128 ]]; then pr_warning "Proxy timed out. Unable to CONNECT via proxy. " - close_socket + close_socket 5 return 6 elif [[ "${proyxline%/*}" == HTTP ]]; then proyxline=${proyxline#* } if [[ "${proyxline%% *}" != 200 ]]; then pr_warning "Unable to CONNECT via proxy. " [[ "$PORT" != 443 ]] && prln_warning "Check whether your proxy supports port $PORT and the underlying protocol." - close_socket + close_socket 5 return 6 fi fi @@ -10782,7 +10782,11 @@ fd_socket() { return 1 } +# arg1: socket fd but atm we use 5 anyway, see comment for fd_socket() +# close_socket(){ + local fd="$1" + exec 5<&- exec 5>&- return 0 @@ -14257,7 +14261,7 @@ sslv2_sockets() { parse_sslv2_serverhello "$SOCK_REPLY_FILE" "$parse_complete" ret=$? - close_socket + close_socket 5 tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE return $ret } @@ -15009,7 +15013,7 @@ tls_sockets() { tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then - close_socket + close_socket 5 TMPFILE=$SOCK_REPLY_FILE tmpfile_handle ${FUNCNAME[0]}.dd return $ret @@ -15181,7 +15185,7 @@ tls_sockets() { debugme echo "stuck on sending: $ret" fi - "$close_connection" && close_socket + "$close_connection" && close_socket 5 tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE return $ret } @@ -15387,7 +15391,7 @@ run_heartbleed(){ fi outln tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE - close_socket + close_socket 5 return 0 } @@ -15577,7 +15581,7 @@ run_ccs_injection(){ outln tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE - close_socket + close_socket 5 return $ret } @@ -15784,14 +15788,14 @@ run_ticketbleed() { pr_svrty_best "not vulnerable (OK)" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" send_close_notify "${tls_hello_ascii:18:4}" - close_socket + close_socket 5 break elif [[ -z "${tls_hello_ascii:0:2}" ]]; then pr_svrty_best "not vulnerable (OK)" out ", reply empty" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" send_close_notify "${tls_hello_ascii:18:4}" - close_socket + close_socket 5 break elif [[ "${tls_hello_ascii:0:2}" == 16 ]]; then early_exit=false @@ -15819,11 +15823,11 @@ run_ticketbleed() { out " around line $LINENO (debug info: ${tls_hello_ascii:0:2}, ${tls_hello_ascii:2:10})" fileout "$jsonID" "DEBUG" "test failed, around $LINENO (debug info: ${tls_hello_ascii:0:2}, ${tls_hello_ascii:2:10})" "$cve" "$cwe" send_close_notify "${tls_hello_ascii:18:4}" - close_socket + close_socket 5 break fi send_close_notify "${tls_hello_ascii:18:4}" - close_socket + close_socket 5 done if ! "$early_exit"; then @@ -17790,7 +17794,8 @@ run_tls_truncation() { run_starttls_injection() { local uds="" local openssl_bin="" - local -i socat_pid=424242 + local -i socat_pid + local -i openssl_pid local cve="" local cwe="CWE-74" local hint="" @@ -17828,8 +17833,9 @@ run_starttls_injection() { fi # normally the interesting fallback we grep later for is in fd2 but we'll catch all here $openssl_bin s_client -unix $uds >$TMPFILE 2>&1 & + openssl_pid=$! sleep 1 - kill $socat_pid + [[ "$DEBUG" -ge 4 ]] && cat $TMPFILE if grep -Eqa '^250-|^503 ' $TMPFILE; then out "likely " @@ -17839,8 +17845,12 @@ run_starttls_injection() { prln_svrty_good "not vulnerable (OK)" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" fi - tmpfile_handle ${FUNCNAME[0]}.txt + kill $socat_pid + kill $openssl_pid + close_socket 5 + + tmpfile_handle ${FUNCNAME[0]}.txt return 0 } @@ -18346,7 +18356,7 @@ run_robot() { else socksend ",x15, x03, x01, x00, x02, x02, x00" 0 fi - close_socket + close_socket 5 prln_fixme "Conversion of public key failed around line $((LINENO - 9))" fileout "$jsonID" "WARN" "Conversion of public key failed around line $((LINENO - 10)) " return 1 @@ -18400,7 +18410,7 @@ run_robot() { fi debugme echo -e "\nresponse[$testnum] = ${response[testnum]}" [[ $DEBUG -ge 3 ]] && [[ $subret -eq 0 ]] && parse_tls_serverhello "${response[testnum]}" - close_socket + close_socket 5 # Don't continue testing if it has already been determined that # tests need to be rerun with a longer timeout. @@ -18921,7 +18931,8 @@ single check as ("$PROG_NAME URI" does everything except -E and -g): -H, --heartbleed tests for Heartbleed vulnerability -I, --ccs, --ccs-injection tests for CCS injection vulnerability -T, --ticketbleed tests for Ticketbleed vulnerability in BigIP loadbalancers - -BB, --robot tests for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability + --BB, --robot tests for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability + --SI, --starttls-injection tests for STARTTLS injection issues -R, --renegotiation tests for renegotiation vulnerabilities -C, --compression, --crime tests for CRIME vulnerability (TLS compression issue) -B, --breach tests for BREACH vulnerability (HTTP compression issue) @@ -20240,7 +20251,7 @@ determine_service() { fi fi fi - close_socket + close_socket 5 outln if [[ -z "$1" ]]; then @@ -21545,7 +21556,7 @@ parse_cmd_line() { do_ticketbleed=true let "VULN_COUNT++" ;; - -BB|--robot) + -BB|--BB|--robot) do_robot=true ;; -R|--renegotiation) From a65e55522f59975d4ce2515f8c1cb73b8ab3316d Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Sat, 29 Aug 2020 10:20:35 +0200 Subject: [PATCH 06/14] Add sending payloads for POP and IMAP for starttls injection * todo: parse the return strings for detection * test ;-) * check whether the random char thing works under every OS supported * definition of five_random var --- testssl.sh | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/testssl.sh b/testssl.sh index 5545f6a..51440a2 100755 --- a/testssl.sh +++ b/testssl.sh @@ -10533,6 +10533,7 @@ starttls_ftp_dialog() { } # argv1: empty: SMTP, "lmtp" : LMTP +# argv2: payload for STARTTLS injection test # starttls_smtp_dialog() { local greet_str="EHLO testssl.sh" @@ -10542,10 +10543,7 @@ starttls_smtp_dialog() { local -i ret=0 "$SNEAKY" && greet_str="EHLO google.com" - if [[ -n "$2" ]]; then - # Here we can supply and addtional command after STARTTLS for injection tests - starttls="STARTTLS\r\n$2" - fi + [[ -n "$2" ]] && starttls="$starttls\r\n$1" # this adds a payload if supplied if [[ "$1" == lmtp ]]; then proto="lmtp" greet_str="LHLO" @@ -10562,10 +10560,13 @@ starttls_smtp_dialog() { return $ret } +# argv1: payload for STARTTLS injection test +# starttls_pop3_dialog() { local -i ret=0 local starttls="STLS" + [[ -n "$1" ]] && starttls="$starttls\r\n$1" # this adds a payload if supplied debugme echo "=== starting pop3 STARTTLS dialog ===" starttls_full_read '^\+OK' '^\+OK' '' "received server greeting" && starttls_just_send "$starttls" "initiated STARTTLS" && @@ -10575,11 +10576,14 @@ starttls_pop3_dialog() { return $ret } +# argv1: payload for STARTTLS injection test +# starttls_imap_dialog() { local -i ret=0 local reSTARTTLS='^\* CAPABILITY(( .*)? IMAP4rev1( .*)? STARTTLS(.*)?|( .*)? STARTTLS( .*)? IMAP4rev1(.*)?)$' local starttls="a002 STARTTLS" + [[ -n "$1" ]] && starttls="$starttls\r\n$1" # this adds a payload if supplied debugme echo "=== starting imap STARTTLS dialog ===" starttls_full_read '^\* ' '^\* OK ' '' "received server greeting" && starttls_just_send 'a001 CAPABILITY' "sent CAPABILITY" && @@ -10736,13 +10740,13 @@ fd_socket() { starttls_smtp_dialog lmtp ;; pop3|pop3s) # POP, see https://tools.ietf.org/html/rfc2595 - starttls_pop3_dialog + starttls_pop3_dialog "$payload" ;; nntp|nntps) # NNTP, see https://tools.ietf.org/html/rfc4642 starttls_nntp_dialog ;; imap|imaps) # IMAP, https://tools.ietf.org/html/rfc2595, https://tools.ietf.org/html/rfc3501 - starttls_imap_dialog + starttls_imap_dialog "$payload" ;; irc|ircs) # IRC, https://ircv3.net/specs/extensions/tls-3.1.html, https://ircv3.net/specs/core/capability-negotiation.html fatal "FIXME: IRC+STARTTLS not yet supported" $ERR_NOSUPPORT @@ -17822,7 +17826,20 @@ run_starttls_injection() { uds=$TEMPDIR/uds - fd_socket 5 "EHLO google.com" + case $proto in + smtp) fd_socket 5 "EHLO google.com" + ;; + pop) fd_socket 5 "CAPA" + ;; + imap) +#FIXME: check all BSDs: + five_random=$(tr -dc '[:upper:]' < /dev/urandom | dd bs=5 count=1 2>/dev/null) + fd_socket 5 "$five_random NOOP" + ;; + *) outln "STARTTLS injection test doesn't work for $proto, yet" + fileout "$jsonID" "INFO" "STARTTLS injection test doesn't work for $proto" "$cve" "$cwe" "$hint" + ;; + esac $SOCAT FD:5 UNIX-LISTEN:$uds & socat_pid=$! @@ -17831,12 +17848,14 @@ run_starttls_injection() { else openssl_bin=$OPENSSL2 fi - # normally the interesting fallback we grep later for is in fd2 but we'll catch all here + # normally the interesting fallback we grep later for is in fd2 but we'll catch also stdout here $openssl_bin s_client -unix $uds >$TMPFILE 2>&1 & openssl_pid=$! sleep 1 [[ "$DEBUG" -ge 4 ]] && cat $TMPFILE +#FIXME: is the pattern sufficient for SMTP? +#FIXME: check POP / IMAP output for vulnerable servers if grep -Eqa '^250-|^503 ' $TMPFILE; then out "likely " prln_svrty_high "VULNERABLE (NOT ok)" From 7f4cf42ff40f3a896d2e389c290f14b99c360044 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 31 Aug 2020 17:14:56 +0200 Subject: [PATCH 07/14] Works now also for POP3 / IMAP * Ensured the random char generation worked under every OS supported * Got POP3 and IMAP working * always define SERVICE so that we can us it also for SMTP starttls injection * fixed error in starttls_smtp_dialog where arg1 was taken as payload instead of arg2 * squashed error msg when killed socat or openssl process to avoid mess on screen when processes already terminated (* removed some redundant quotes at RHS if [[]] expressions) todo: * more tests for positives * are tests for negatives sufficent? ("prove" is happy except one issue which is probably not related but still need to understand) For the record: t/25_baseline_starttls.t line 50 and 67: "Oops: STARTTLS handshake failed (code: 2)" --- testssl.sh | 62 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/testssl.sh b/testssl.sh index 51440a2..f3e834c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -2167,7 +2167,8 @@ s_client_options() { ###### check code starts here ###### # determines whether the port has an HTTP service running or not (plain TLS, no STARTTLS) -# arg1 could be the protocol determined as "working". IIS6 needs that +# arg1 could be the protocol determined as "working". IIS6 needs that. +# service_detection() { local -i was_killed @@ -8781,7 +8782,7 @@ certificate_info() { prln_italic "$(out_row_aligned_max_width "$all_san" "$indent " $TERM_WIDTH)" fileout "${jsonID}${json_postfix}" "INFO" "$all_san" else - if [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then + if [[ $SERVICE == HTTP ]] || "$ASSUME_HTTP"; then pr_svrty_high "missing (NOT ok)"; outln " -- Browsers are complaining" fileout "${jsonID}${json_postfix}" "HIGH" "No SAN, browsers are complaining" else @@ -8876,7 +8877,7 @@ certificate_info() { pr_svrty_high "$trustfinding" trust_sni_finding="HIGH" elif ( [[ $trust_sni -eq 4 ]] || [[ $trust_sni -eq 8 ]] ); then - if [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then + if [[ $SERVICE == HTTP ]] || "$ASSUME_HTTP"; then # https://bugs.chromium.org/p/chromium/issues/detail?id=308330 # https://bugzilla.mozilla.org/show_bug.cgi?id=1245280 # https://www.chromestatus.com/feature/4981025180483584 @@ -10543,7 +10544,7 @@ starttls_smtp_dialog() { local -i ret=0 "$SNEAKY" && greet_str="EHLO google.com" - [[ -n "$2" ]] && starttls="$starttls\r\n$1" # this adds a payload if supplied + [[ -n "$2" ]] && starttls="$starttls\r\n$2" # this adds a payload if supplied if [[ "$1" == lmtp ]]; then proto="lmtp" greet_str="LHLO" @@ -17800,6 +17801,7 @@ run_starttls_injection() { local openssl_bin="" local -i socat_pid local -i openssl_pid + local vuln=false local cve="" local cwe="CWE-74" local hint="" @@ -17824,39 +17826,44 @@ run_starttls_injection() { fi pr_bold " STARTTLS injection" ; out " (experimental) " - uds=$TEMPDIR/uds - - case $proto in + case $SERVICE in smtp) fd_socket 5 "EHLO google.com" ;; - pop) fd_socket 5 "CAPA" + pop3) fd_socket 5 "CAPA" ;; - imap) -#FIXME: check all BSDs: - five_random=$(tr -dc '[:upper:]' < /dev/urandom | dd bs=5 count=1 2>/dev/null) - fd_socket 5 "$five_random NOOP" - ;; - *) outln "STARTTLS injection test doesn't work for $proto, yet" - fileout "$jsonID" "INFO" "STARTTLS injection test doesn't work for $proto" "$cve" "$cwe" "$hint" - ;; + imap) five_random=$(tr -dc '[:upper:]' < /dev/urandom | dd bs=5 count=1 2>/dev/null) + fd_socket 5 "$five_random NOOP" + ;; + *) outln "STARTTLS injection test doesn't work for $SERVICE, yet" + fileout "$jsonID" "INFO" "STARTTLS injection test doesn't work for $SERVICE" "$cve" "$cwe" "$hint" + return 1 + ;; esac + + uds="$TEMPDIR/uds" $SOCAT FD:5 UNIX-LISTEN:$uds & socat_pid=$! if "$HAS_UDS"; then - openssl_bin=$OPENSSL - else - openssl_bin=$OPENSSL2 + openssl_bin="$OPENSSL" + elif "$HAS_UDS2"; then + openssl_bin="$OPENSSL2" fi # normally the interesting fallback we grep later for is in fd2 but we'll catch also stdout here $openssl_bin s_client -unix $uds >$TMPFILE 2>&1 & openssl_pid=$! sleep 1 - [[ "$DEBUG" -ge 4 ]] && cat $TMPFILE -#FIXME: is the pattern sufficient for SMTP? -#FIXME: check POP / IMAP output for vulnerable servers - if grep -Eqa '^250-|^503 ' $TMPFILE; then + [[ "$DEBUG" -ge 2 ]] && tail $TMPFILE +#FIXME: is the pattern sufficient for SMTP / POP / IMAP? + case $SERVICE in + # Mind all ' ' here! + smtp) grep -Eqa '^250-|^503 ' $TMPFILE && vuln=true ;; + pop3) grep -Eqa '^USER|^PIPELINING|^\+OK ' $TMPFILE && vuln=true ;; + imap) grep -Eqa ' OK NOOP ' $TMPFILE && vuln=true ;; + esac + + if "$vuln"; then out "likely " prln_svrty_high "VULNERABLE (NOT ok)" fileout "$jsonID" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint" @@ -17865,8 +17872,8 @@ run_starttls_injection() { fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" fi - kill $socat_pid - kill $openssl_pid + kill $socat_pid 2>/dev/null + kill $openssl_pid 2>/dev/null close_socket 5 tmpfile_handle ${FUNCNAME[0]}.txt @@ -20358,6 +20365,11 @@ determine_service() { ;; esac fi + + # It comes handy later also for STARTTLS injection to define this global. When we do banner grabbing + # or replace service_detection() we might not need that anymore + SERVICE=$protocol + tmpfile_handle ${FUNCNAME[0]}.txt return 0 # OPTIMAL_PROTO, GET_REQ*/HEAD_REQ* is set now } From 191223017382251c7cd774eea5fe6e7bae152dbe Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 31 Aug 2020 18:29:59 +0200 Subject: [PATCH 08/14] Show that we need socat for this check but still do a soft fail here, also no warning, as we do not expect to have everybody have socat installed --- testssl.sh | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/testssl.sh b/testssl.sh index f3e834c..2a8c925 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17809,16 +17809,6 @@ run_starttls_injection() { [[ -z "$STARTTLS" ]] && return 0 - if [[ -z "$SOCAT" ]]; then - fileout "$jsonID" "WARN" "Need socat for this" "$cve" "$cwe" "$hint" - debugme1 echo "Need socat for this check" - return 1 - fi - if [[ -z "$HAS_UDS2" ]] && [[ -z "$HAS_UDS" ]]; then - fileout "$jsonID" "WARN" "Need OpenSSL with Unix-domain socket s_client support for this check" "$cve" "$cwe" "$hint" - debugme1 echo "Need an OpenSSL with Unix-domain socket s_client support for this check" - return 1 - fi if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then outln pr_headlineln " Checking for STARTTLS injection " @@ -17826,6 +17816,18 @@ run_starttls_injection() { fi pr_bold " STARTTLS injection" ; out " (experimental) " + # We'll do a soft fail here, also no warning, as I do not expect to have everybody have socat installed + if [[ -z "$SOCAT" ]]; then + fileout "$jsonID" "WARN" "Need socat for this" "$cve" "$cwe" "$hint" + outln "Need socat for this check" + return 1 + fi + if [[ -z "$HAS_UDS2" ]] && [[ -z "$HAS_UDS" ]]; then + fileout "$jsonID" "WARN" "Need OpenSSL with Unix-domain socket s_client support for this check" "$cve" "$cwe" "$hint" + outln "Need an OpenSSL with Unix-domain socket s_client support for this check" + return 1 + fi + case $SERVICE in smtp) fd_socket 5 "EHLO google.com" ;; From 3e6b1b971a93f9e71247d50fc14ecb99b5d78cad Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 2 Sep 2020 17:35:42 +0200 Subject: [PATCH 09/14] Make Travis work again (STARTTLS injection) For not vulnerable hosts the low level starttls_* functions returned an error when the STARTTLS injection was tested which confused Travis/CI ( "Oops: STARTTLS handshake failed (code: 2)" ) --- testssl.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 2a8c925..72f7ecc 100755 --- a/testssl.sh +++ b/testssl.sh @@ -10775,7 +10775,11 @@ fd_socket() { case $ret in 0) return 0 ;; 3) fatal "No STARTTLS found in handshake" $ERR_CONNECT ;; - *) ((NR_STARTTLS_FAIL++)) + *) if [[ $ret -eq 2 ]] && [[ -n "$payload" ]]; then + # We don't want this handling for STARTTLS injection + return 0 + fi + ((NR_STARTTLS_FAIL++)) # This are mostly timeouts here (code >=128). We give the client a chance to try again later. For cases # where we have no STARTTLS in the server banner however - ret code=3 - we don't neet to try again connectivity_problem $NR_STARTTLS_FAIL $MAX_STARTTLS_FAIL "STARTTLS handshake failed (code: $ret)" "repeated STARTTLS problems, giving up ($ret)" From 4a167f6ac54984e2198139cf045a6baa58b844cf Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 2 Sep 2020 17:44:11 +0200 Subject: [PATCH 10/14] Add openssl 1.1.1g into alpine docker image for STARTTLS injection --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4bb321f..538b479 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM alpine:3.11 RUN apk update && \ apk upgrade && \ - apk add --no-cache bash procps drill git coreutils libidn curl socat && \ + apk add --no-cache bash procps drill git coreutils libidn curl socat openssl && \ addgroup testssl && \ adduser -G testssl -g "testssl user" -s /bin/bash -D testssl && \ ln -s /home/testssl/testssl.sh /usr/local/bin/ && \ From 35b79f65eecaf21f095a7e0621a0663f3f59862c Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 2 Sep 2020 18:23:11 +0200 Subject: [PATCH 11/14] Add documentation for STARTTLS injection's cmd line flag and also the modified one for ROBOT --- doc/testssl.1 | 5 ++++- doc/testssl.1.html | 4 +++- doc/testssl.1.md | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/testssl.1 b/doc/testssl.1 index 2f4fea5..33d6404 100644 --- a/doc/testssl.1 +++ b/doc/testssl.1 @@ -349,7 +349,10 @@ Security headers (X\-Frame\-Options, X\-XSS\-Protection, Expect\-CT,\.\.\. , CSP \fB\-T, \-\-ticketbleed\fR Checks for Ticketbleed memory leakage in BigIP loadbalancers\. . .P -\fB\-BB, \-\-robot\fR Checks for vulnerability to ROBOT / (\fIReturn Of Bleichenbacher\'s Oracle Threat\fR) attack\. +\fB\-\-BB, \-\-robot\fR Checks for vulnerability to ROBOT / (\fIReturn Of Bleichenbacher\'s Oracle Threat\fR) attack\. +. +.P +\fB\-\-SI, \-\-starttls\-injection\fR Checks for STARTTLS injection vulnerabilities (SMTP, IMAP, POP3 only)\. \fIsocat\fR and OpenSSL >=1.1.0 is needed\. . .P \fB\-R, \-\-renegotiation\fR Tests renegotiation vulnerabilities\. Currently there\'s a check for \fISecure Renegotiation\fR and for \fISecure Client\-Initiated Renegotiation\fR\. Please be aware that vulnerable servers to the latter can likely be DoSed very easily (HTTP)\. A check for \fIInsecure Client\-Initiated Renegotiation\fR is not yet implemented\. diff --git a/doc/testssl.1.html b/doc/testssl.1.html index 2d66208..52f929e 100644 --- a/doc/testssl.1.html +++ b/doc/testssl.1.html @@ -315,7 +315,9 @@ Also for multiple server certificates are being checked for as well as for the c

-T, --ticketbleed Checks for Ticketbleed memory leakage in BigIP loadbalancers.

-

-BB, --robot Checks for vulnerability to ROBOT / (Return Of Bleichenbacher's Oracle Threat) attack.

+

--BB, --robot Checks for vulnerability to ROBOT / (Return Of Bleichenbacher's Oracle Threat) attack.

+ +

--SI, --starttls-injection Checks for STARTTLS injection vulnerabilities (SMTP, IMAP, POP3 only). socat and OpenSSL ≥1.1.0 is needed.

-R, --renegotiation Tests renegotiation vulnerabilities. Currently there's a check for Secure Renegotiation and for Secure Client-Initiated Renegotiation. Please be aware that vulnerable servers to the latter can likely be DoSed very easily (HTTP). A check for Insecure Client-Initiated Renegotiation is not yet implemented.

diff --git a/doc/testssl.1.md b/doc/testssl.1.md index 42f8c7e..cdca42b 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -229,7 +229,9 @@ Also for multiple server certificates are being checked for as well as for the c `-T, --ticketbleed` Checks for Ticketbleed memory leakage in BigIP loadbalancers. -`-BB, --robot` Checks for vulnerability to ROBOT / (*Return Of Bleichenbacher's Oracle Threat*) attack. +`--BB, --robot` Checks for vulnerability to ROBOT / (*Return Of Bleichenbacher's Oracle Threat*) attack. + +`--SI, --starttls-injection` Checks for STARTTLS injection vulnerabilities (SMTP, IMAP, POP3 only). `socat` and OpenSSL >=1.1.0 is needed. `-R, --renegotiation` Tests renegotiation vulnerabilities. Currently there's a check for *Secure Renegotiation* and for *Secure Client-Initiated Renegotiation*. Please be aware that vulnerable servers to the latter can likely be DoSed very easily (HTTP). A check for *Insecure Client-Initiated Renegotiation* is not yet implemented. From eafeb904f466d684388f5e47fae813ecf6075a39 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Thu, 3 Sep 2020 14:22:53 +0200 Subject: [PATCH 12/14] Fix emptying of SERVICE variable in determine_service() SERVICE global was previously set to $protocol which was meant to set this for STARTTLS services. However it was executes outside the corresponding if-statement. This commit moves the statement where it belongs. --- testssl.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testssl.sh b/testssl.sh index 72f7ecc..e3b9c13 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20370,11 +20370,11 @@ determine_service() { fatal "momentarily only ftp, smtp, lmtp, pop3, imap, xmpp, xmpp-server, telnet, ldap, nntp, postgres and mysql allowed" $ERR_CMDLINE ;; esac - fi + # It comes handy later also for STARTTLS injection to define this global. When we do banner grabbing + # or replace service_detection() we might not need that anymore + SERVICE=$protocol - # It comes handy later also for STARTTLS injection to define this global. When we do banner grabbing - # or replace service_detection() we might not need that anymore - SERVICE=$protocol + fi tmpfile_handle ${FUNCNAME[0]}.txt return 0 # OPTIMAL_PROTO, GET_REQ*/HEAD_REQ* is set now From 9ea74462038f9fcf57c849e973e627e8dee6e39b Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Thu, 26 Nov 2020 10:48:32 +0100 Subject: [PATCH 13/14] Add STARTTLS injection to Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b21696..cdbd545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ * Percent output char problem fixed * Several display/output fixes * BREACH check: list all compression methods and add brotli -* test for winshock vulnerability +* Test for old winshock vulnerability +* Test for STARTTLS injection vulnerabilities (SMTP, POP3, IMAP) * Security fix: DNS input * Don't use external pwd anymore * STARTTLS: XMPP server support @@ -26,7 +27,7 @@ * Added environment variable for amount of attempts for ssl renegotiation check * Added --user-agent argument to support using a custom User Agent * Added --overwrite argument to support overwriting output files without warning -* Headerflag X-XSS-Protection is labeled as INFO +* Headerflag X-XSS-Protection is labeled as INFO ### Features implemented / improvements in 3.0 From 7c66535628b732c474a54080478c7e862eec1f8a Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Tue, 29 Dec 2020 13:44:04 +0100 Subject: [PATCH 14/14] resolve merge conflict --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 538b479..019668a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,8 @@ FROM alpine:3.11 RUN apk update && \ apk upgrade && \ - apk add --no-cache bash procps drill git coreutils libidn curl socat openssl && \ + apk add bash procps drill git coreutils libidn curl socat openssl && \ + rm -rf /var/cache/apk/* && \ addgroup testssl && \ adduser -G testssl -g "testssl user" -s /bin/bash -D testssl && \ ln -s /home/testssl/testssl.sh /usr/local/bin/ && \