From 429db592e2f5cdefcfb1535b2d96ce11737df00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Tue, 28 Nov 2023 14:41:25 +0100 Subject: [PATCH 1/8] Crudely detect exponential backoff as a mitigation --- testssl.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 6b694e4..3e14be7 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17068,13 +17068,23 @@ run_renego() { else (for ((i=0; i < ssl_reneg_attempts; i++ )); do echo R; sleep 1; done) | \ $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE - case $? in + tmp_result=$? + # If we are here, we have done two successfull renegotiation (-2) and do the loop + loop_reneg=$(($(grep -a '^RENEGOTIATING' $ERRFILE | wc -l)-2)) + # If we got less than 2/3 successfull attemps during the loop with 1s pause, we are in precence of exponential backoff. + if [[ $loop_reneg -le $(($ssl_reneg_attempts*2/3)) ]]; then + tmp_result=2 + fi + case $tmp_result in 0) pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat ($ssl_reneg_attempts attempts)" fileout "$jsonID" "HIGH" "VULNERABLE, DoS threat" "$cve" "$cwe" "$hint" ;; 1) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated (disconnect within $ssl_reneg_attempts)" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" ;; + 2) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in ${ssl_reneg_attempts}s)" + fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" + ;; *) prln_warning "FIXME (bug): $sec_client_renego ($ssl_reneg_attempts tries)" fileout "$jsonID" "DEBUG" "FIXME (bug $ssl_reneg_attempts tries) $sec_client_renego" "$cve" "$cwe" ret=1 From 52c6ac7fec94d578dc00ff28a40d2f55431a8931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Tue, 28 Nov 2023 15:22:01 +0100 Subject: [PATCH 2/8] Spell fix. --- testssl.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index 3e14be7..a644086 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17069,9 +17069,9 @@ run_renego() { (for ((i=0; i < ssl_reneg_attempts; i++ )); do echo R; sleep 1; done) | \ $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE tmp_result=$? - # If we are here, we have done two successfull renegotiation (-2) and do the loop + # If we are here, we have done two successful renegotiation (-2) and do the loop loop_reneg=$(($(grep -a '^RENEGOTIATING' $ERRFILE | wc -l)-2)) - # If we got less than 2/3 successfull attemps during the loop with 1s pause, we are in precence of exponential backoff. + # If we got less than 2/3 successful attempts during the loop with 1s pause, we are in presence of exponential backoff. if [[ $loop_reneg -le $(($ssl_reneg_attempts*2/3)) ]]; then tmp_result=2 fi From 2c84a525cc039d25c7eaa5fb6ce0d15f91487e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Thu, 7 Dec 2023 18:58:58 +0100 Subject: [PATCH 3/8] Fix mitigation detection with debug level 0 --- testssl.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index a644086..0cafa52 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17036,6 +17036,13 @@ run_renego() { fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" sec_client_renego=1 else + # We will need $ERRFILE for mitigation detection + if [[ $ERRFILE =~ dev.null ]]; then + ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE + restore_errfile=1 + else + restore_errfile=0 + fi # We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background # msg enables us to look deeper into it while debugging echo R | $OPENSSL s_client $(s_client_options "$proto $BUGS $legacycmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE & @@ -17110,7 +17117,9 @@ run_renego() { # # https://www.openssl.org/news/vulnerabilities.html#y2009. It can only be tested with OpenSSL <=0.9.8k # Insecure Client-Initiated Renegotiation is missing ==> sockets. When we complete the handshake ;-) - + if [[ $restore_errfile -eq 1 ]]; then + ERRFILE="/dev/null" + fi tmpfile_handle ${FUNCNAME[0]}.txt return $ret } From d30d8e09f2178b761c58ed18f8caf733527a1d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Wed, 10 Jan 2024 18:31:41 +0100 Subject: [PATCH 4/8] tab/space corrections and "grep -ac" in place of "grep -a | wc -l" --- testssl.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/testssl.sh b/testssl.sh index 0cafa52..7c1d356 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17036,13 +17036,13 @@ run_renego() { fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" sec_client_renego=1 else - # We will need $ERRFILE for mitigation detection - if [[ $ERRFILE =~ dev.null ]]; then - ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE - restore_errfile=1 - else - restore_errfile=0 - fi + # We will need $ERRFILE for mitigation detection + if [[ $ERRFILE =~ dev.null ]]; then + ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE + restore_errfile=1 + else + restore_errfile=0 + fi # We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background # msg enables us to look deeper into it while debugging echo R | $OPENSSL s_client $(s_client_options "$proto $BUGS $legacycmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE & @@ -17075,12 +17075,12 @@ run_renego() { else (for ((i=0; i < ssl_reneg_attempts; i++ )); do echo R; sleep 1; done) | \ $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE - tmp_result=$? - # If we are here, we have done two successful renegotiation (-2) and do the loop - loop_reneg=$(($(grep -a '^RENEGOTIATING' $ERRFILE | wc -l)-2)) - # If we got less than 2/3 successful attempts during the loop with 1s pause, we are in presence of exponential backoff. - if [[ $loop_reneg -le $(($ssl_reneg_attempts*2/3)) ]]; then - tmp_result=2 + tmp_result=$? + # If we are here, we have done two successful renegotiation (-2) and do the loop + loop_reneg=$(($(grep -ac '^RENEGOTIATING' $ERRFILE )-2)) + # If we got less than 2/3 successful attempts during the loop with 1s pause, we are in presence of exponential backoff. + if [[ $loop_reneg -le $(($ssl_reneg_attempts*2/3)) ]]; then + tmp_result=2 fi case $tmp_result in 0) pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat ($ssl_reneg_attempts attempts)" @@ -17118,7 +17118,7 @@ run_renego() { # https://www.openssl.org/news/vulnerabilities.html#y2009. It can only be tested with OpenSSL <=0.9.8k # Insecure Client-Initiated Renegotiation is missing ==> sockets. When we complete the handshake ;-) if [[ $restore_errfile -eq 1 ]]; then - ERRFILE="/dev/null" + ERRFILE="/dev/null" fi tmpfile_handle ${FUNCNAME[0]}.txt return $ret From b793f54c3eaea29939b540353e5a62740c3027de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Thu, 11 Jan 2024 18:30:44 +0100 Subject: [PATCH 5/8] Add timeout for the client initiated renego loop Some site hang/block the connection after some renego reties Example: https://feedback.amadeus.com Hand written timeout logic because: - we want to get the result of the command in case of normal exit - we want to have working log fd redirection - we want to known the timeout condition --- testssl.sh | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/testssl.sh b/testssl.sh index 7c1d356..c87668c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17074,13 +17074,23 @@ run_renego() { fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint" else (for ((i=0; i < ssl_reneg_attempts; i++ )); do echo R; sleep 1; done) | \ - $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE + $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE & + pid=$! + ( sleep $(($ssl_reneg_attempts*3)) && kill $pid && touch $TEMPDIR/was_killed ) >&2 2>/dev/null & + watcher=$! + # Trick to get the return value of the openssl command, output redirection and a timeout. Yes, somme target hang/block after some tries. + wait $pid && pkill -HUP -P $watcher tmp_result=$? # If we are here, we have done two successful renegotiation (-2) and do the loop loop_reneg=$(($(grep -ac '^RENEGOTIATING' $ERRFILE )-2)) - # If we got less than 2/3 successful attempts during the loop with 1s pause, we are in presence of exponential backoff. - if [[ $loop_reneg -le $(($ssl_reneg_attempts*2/3)) ]]; then - tmp_result=2 + if [[ -f $TEMPDIR/was_killed ]]; then + tmp_result=3 + rm -f $TEMPDIR/was_killed + else + # If we got less than 2/3 successful attempts during the loop with 1s pause, we are in presence of exponential backoff. + if [[ $loop_reneg -le $(($ssl_reneg_attempts*2/3)) ]]; then + tmp_result=2 + fi fi case $tmp_result in 0) pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat ($ssl_reneg_attempts attempts)" @@ -17092,6 +17102,9 @@ run_renego() { 2) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in ${ssl_reneg_attempts}s)" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" ;; + 3) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in $((${ssl_reneg_attempts}*3))s(timeout))" + fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" + ;; *) prln_warning "FIXME (bug): $sec_client_renego ($ssl_reneg_attempts tries)" fileout "$jsonID" "DEBUG" "FIXME (bug $ssl_reneg_attempts tries) $sec_client_renego" "$cve" "$cwe" ret=1 From 9b79e3917a563cd1617c39a118e52d0c7dc330b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Thu, 11 Jan 2024 18:34:47 +0100 Subject: [PATCH 6/8] Bump SSL_RENEG_ATTEMPTS=10 for Stormshield Stormshield allows 9x and then blocks. So then 10x should be tested. Example: https://ems.ocapiat.fr --- testssl.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index c87668c..90a5645 100755 --- a/testssl.sh +++ b/testssl.sh @@ -232,7 +232,7 @@ fi DISPLAY_CIPHERNAMES="openssl" # display OpenSSL ciphername (but both OpenSSL and RFC ciphernames in wide mode) declare UA_STD="TLS tester from $SWURL" declare -r UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0" -SSL_RENEG_ATTEMPTS=${SSL_RENEG_ATTEMPTS:-6} # number of times to check SSL Renegotiation +SSL_RENEG_ATTEMPTS=${SSL_RENEG_ATTEMPTS:-10} # number of times to check SSL Renegotiation ########### Initialization part, further global vars just being declared here # @@ -17067,6 +17067,7 @@ run_renego() { # Mitigations (default values) for: # - node.js allows 3x R and then blocks. So then 4x should be tested. # - F5 BIG-IP ADS allows 5x R and then blocks. So then 6x should be tested. + # - Stormshield allows 9x and then blocks. So then 10x should be tested. # This way we save a couple seconds as we weeded out the ones which are more robust # Amount of times tested before breaking is set in SSL_RENEG_ATTEMPTS. if [[ $SERVICE != HTTP ]]; then From de364b0c849c36f2896788d4df4428f24330cc9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Fri, 12 Jan 2024 11:30:35 +0100 Subject: [PATCH 7/8] Introduce SSL_REGEG_WAIT and reduce wait to 0.25s Reduce wait between reneg test to 0.25s. Still robust and accelerates the test as now we do up to 10 renego tests. With the global loop timeout, the backoff identification seem unneeded. But if we switch to 0.25s, we no longuer trigger the global timeout so it is still valuable. Adjust write out messages as bash do not support floating point number arithmetic. --- testssl.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/testssl.sh b/testssl.sh index 90a5645..0449ede 100755 --- a/testssl.sh +++ b/testssl.sh @@ -233,6 +233,7 @@ DISPLAY_CIPHERNAMES="openssl" # display OpenSSL ciphername (but both O declare UA_STD="TLS tester from $SWURL" declare -r UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0" SSL_RENEG_ATTEMPTS=${SSL_RENEG_ATTEMPTS:-10} # number of times to check SSL Renegotiation +SSL_RENEG_WAIT=${SSL_RENEG_WAIT:-0.25} # time between SSL Renegotiation checks ########### Initialization part, further global vars just being declared here # @@ -16952,6 +16953,7 @@ run_renego() { local hint="" local jsonID="" local ssl_reneg_attempts=$SSL_RENEG_ATTEMPTS + local ssl_reneg_wait=$SSL_RENEG_WAIT # In cases where there's no default host configured we need SNI here as openssl then would return otherwise an error and the test will fail "$HAS_TLS13" && [[ -z "$proto" ]] && proto="-no_tls1_3" @@ -17074,7 +17076,7 @@ run_renego() { pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat" fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint" else - (for ((i=0; i < ssl_reneg_attempts; i++ )); do echo R; sleep 1; done) | \ + (for ((i=0; i < ssl_reneg_attempts; i++ )); do echo R; sleep $ssl_reneg_wait; done) | \ $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE & pid=$! ( sleep $(($ssl_reneg_attempts*3)) && kill $pid && touch $TEMPDIR/was_killed ) >&2 2>/dev/null & @@ -17100,10 +17102,12 @@ run_renego() { 1) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated (disconnect within $ssl_reneg_attempts)" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" ;; - 2) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in ${ssl_reneg_attempts}s)" + 2) pr_svrty_good "not vulnerable (OK)"; \ + outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in ${ssl_reneg_attempts}x${ssl_reneg_wait}s)" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" ;; - 3) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in $((${ssl_reneg_attempts}*3))s(timeout))" + 3) pr_svrty_good "not vulnerable (OK)"; \ + outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in $((${ssl_reneg_attempts}*3))s(timeout))" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" ;; *) prln_warning "FIXME (bug): $sec_client_renego ($ssl_reneg_attempts tries)" From 67c362c89ab413536d101848032bc75050903257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 15 Jan 2024 10:07:09 +0100 Subject: [PATCH 8/8] One more spell fix --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 0449ede..8107912 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17081,7 +17081,7 @@ run_renego() { pid=$! ( sleep $(($ssl_reneg_attempts*3)) && kill $pid && touch $TEMPDIR/was_killed ) >&2 2>/dev/null & watcher=$! - # Trick to get the return value of the openssl command, output redirection and a timeout. Yes, somme target hang/block after some tries. + # Trick to get the return value of the openssl command, output redirection and a timeout. Yes, some target hang/block after some tries. wait $pid && pkill -HUP -P $watcher tmp_result=$? # If we are here, we have done two successful renegotiation (-2) and do the loop