From dab177fda96dce0d5bb21f3ae8fcde86c9b6f4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 4 Nov 2024 17:27:18 +0100 Subject: [PATCH 01/11] Big client renego cleanup / refactoring All cases could be handled by the single openssl s_client invocation loop: - dispatch and adjust comments to not loose them - remove the first s_client invocation: stuck connections are allready handled by the main loop - remove the second s_client invocation: normal case and server closed connections are allready handled by the main loop. The loop take care of the race between server connection close and s_client terminating too by doing another loop run, not closing STDIN. - special non HTTP case equivalent to ssl_reneg_attempts=2 - specialcase only the HTTP result printing to not change the output - openssl-timeout option clashe badly with the main loop logic: Introduce $OPENSSL_NOTIMEOUT --- testssl.sh | 181 +++++++++++++++++++++++++---------------------------- 1 file changed, 87 insertions(+), 94 deletions(-) diff --git a/testssl.sh b/testssl.sh index c2e43e3..c90581c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17076,7 +17076,7 @@ run_ticketbleed() { # run_renego() { local legacycmd="" proto="$OPTIMAL_PROTO" - local sec_renego sec_client_renego + local sec_renego local -i ret=0 local cve="" local cwe="CWE-310" @@ -17168,7 +17168,6 @@ run_renego() { elif [[ "$CLIENT_AUTH" == required ]] && [[ -z "$MTLS" ]]; then prln_warning "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" fileout "$jsonID" "WARN" "not having provided client certificate and private key file, the 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 @@ -17179,99 +17178,91 @@ run_renego() { 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 & - wait_kill $! $HEADER_MAXSLEEP - if [[ $? -eq 3 ]]; then - pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out" # it hung - fileout "$jsonID" "OK" "likely not vulnerable (timed out)" "$cve" "$cwe" - sec_client_renego=1 + if [[ $SERVICE != HTTP ]]; then + # Connection could be closed by the server after one try so we will try two iteration + # to not close the openssl s_client STDIN too early like on the HTTP case. + # See https://github.com/drwetter/testssl.sh/issues/2590 + ssl_reneg_attempts=2 + fi + # We try again if server is HTTP. This could be either a node.js server or something else. + # 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. + + # Clear the log to not get the content of previous run before the execution of the new one. + echo -n > $TMPFILE + # RENEGOTIATING wait loop watchdog file + touch $TEMPDIR/allowed_to_loop + # If we dont wait for the session to be established on slow server, we will try to re-negotiate + # too early losing all the attempts before the session establishment as OpenSSL will not buffer them + # (only the first will be till the establishement of the session). + (j=0; while [[ $(grep -ac '^SSL-Session:' $TMPFILE) -ne 1 ]] && [[ $j -lt 30 ]]; do sleep $ssl_reneg_wait; ((j++)); done; \ + for ((i=0; i < ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \ + # 0 means client is renegotiating & doesn't return an error --> vuln! + # 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output + # Exemption from above: server closed the connection but return value was zero + # See https://github.com/drwetter/testssl.sh/issues/1725 and referenced issue @haproxy + while [[ $(grep -ac '^RENEGOTIATING' $ERRFILE) -ne $((i+1)) ]] && [[ -f $TEMPDIR/allowed_to_loop ]] \ + && [[ $(tail -n1 $ERRFILE |grep -acE '^(RENEGOTIATING|depth|verify|notAfter)') -eq 1 ]] \ + && [[ $k -lt 120 ]]; \ + do sleep $ssl_reneg_wait; ((k++)); if (tail -5 $TMPFILE| grep -qa '^closed'); then sleep 1; break; fi; done; \ + done) | \ + $OPENSSL_NOTIMEOUT 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, some target hang/block after some tries (some LiteSpeed servers don't answer at all on "R" and block). + wait $pid + tmp_result=$? + pkill -HUP -P $watcher + wait $watcher + rm -f $TEMPDIR/allowed_to_loop + # If we are here, we have done the loop + loop_reneg=$(grep -ac '^RENEGOTIATING' $ERRFILE) + # As above, some servers close the connection and return value is zero + if (tail -5 $TMPFILE| grep -qa '^closed'); then + tmp_result=1 + fi + if [[ -f $TEMPDIR/was_killed ]]; then + tmp_result=2 + rm -f $TEMPDIR/was_killed + fi + if [[ $SERVICE != HTTP ]]; then + case $tmp_result in + 0) pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat" + fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint" + ;; + 1) prln_svrty_good "not vulnerable (OK)" + fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" + ;; + 2) pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out ($((${ssl_reneg_attempts}*3))s)" # it hung + fileout "$jsonID" "OK" "likely not vulnerable (timed out)" "$cve" "$cwe" + ;; + *) prln_warning "FIXME (bug): $sec_client_renego" + fileout "$jsonID" "DEBUG" "FIXME (bug) $sec_client_renego - Please report" "$cve" "$cwe" + ret=1 + ;; + esac else - # second try in the foreground as we are sure now it won't hang - echo R | $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE - sec_client_renego=$? - # 0 means client is renegotiating & doesn't return an error --> vuln! - # 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output - if tail -5 $TMPFILE| grep -qa '^closed'; then - # Exemption from above: server closed the connection but return value was zero - # See https://github.com/drwetter/testssl.sh/issues/1725 and referenced issue @haproxy - sec_client_renego=1 - fi - case "$sec_client_renego" in - 0) # We try again if server is HTTP. This could be either a node.js server or something else. - # 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 - pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat" - fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint" - else - # Clear the log to not get the content of previous run before the execution of the new one. - echo -n > $TMPFILE - #RENEGOTIATING wait loop watchdog file - touch $TEMPDIR/allowed_to_loop - # If we dont wait for the session to be established on slow server, we will try to re-negotiate - # too early losing all the attempts before the session establishment as OpenSSL will not buffer them - # (only the first will be till the establishement of the session). - (j=0; while [[ $(grep -ac '^SSL-Session:' $TMPFILE) -ne 1 ]] && [[ $j -lt 30 ]]; do sleep $ssl_reneg_wait; ((j++)); done; \ - for ((i=0; i < ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \ - while [[ $(grep -ac '^RENEGOTIATING' $ERRFILE) -ne $((i+3)) ]] && [[ -f $TEMPDIR/allowed_to_loop ]] \ - && [[ $(tail -n1 $ERRFILE |grep -acE '^(RENEGOTIATING|depth|verify|notAfter)') -eq 1 ]] \ - && [[ $k -lt 120 ]]; \ - do sleep $ssl_reneg_wait; ((k++)); if (tail -5 $TMPFILE| grep -qa '^closed'); then sleep 1; break; fi; done; \ - 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 & - watcher=$! - # 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 - tmp_result=$? - pkill -HUP -P $watcher - wait $watcher - rm -f $TEMPDIR/allowed_to_loop - # If we are here, we have done two successful renegotiation (-2) and do the loop - loop_reneg=$(($(grep -ac '^RENEGOTIATING' $ERRFILE)-2)) - # As above, some servers close the connection and return value is zero - if (tail -5 $TMPFILE| grep -qa '^closed'); then - tmp_result=1 - fi - if [[ -f $TEMPDIR/was_killed ]]; then - tmp_result=2 - rm -f $TEMPDIR/was_killed - 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 after $loop_reneg/$ssl_reneg_attempts 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}*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 - ;; - esac - fi - ;; - 1) - prln_svrty_good "not vulnerable (OK)" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - ;; - *) - prln_warning "FIXME (bug): $sec_client_renego" - fileout "$jsonID" "DEBUG" "FIXME (bug) $sec_client_renego - Please report" "$cve" "$cwe" - ret=1 - ;; + 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 after $loop_reneg/$ssl_reneg_attempts 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}*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 + ;; esac fi fi @@ -20447,6 +20438,8 @@ find_openssl_binary() { fi fi + OPENSSL_NOTIMEOUT=$OPENSSL + if ! "$do_mass_testing"; then if [[ -n $OPENSSL_TIMEOUT ]]; then OPENSSL="$TIMEOUT_CMD $OPENSSL_TIMEOUT $OPENSSL" From 09719a322bc2ae7006a243a2fc01bc19b0485af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 4 Nov 2024 20:25:31 +0100 Subject: [PATCH 02/11] Remove the last 1s euristic In the wait loop, I was relying on a 1s sleep to eliminate a possible late zero return value server close on the last attempt. - do globaly one more harmless "for" iteration and remove the sleep 1 for faster and more robust result - correct the non HTTP case iteration value - adjust the timeout to the conservative 6s in the non HTTP case, for HTTP case it become 33s - improve comments --- testssl.sh | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/testssl.sh b/testssl.sh index c90581c..1391175 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17179,10 +17179,7 @@ run_renego() { restore_errfile=0 fi if [[ $SERVICE != HTTP ]]; then - # Connection could be closed by the server after one try so we will try two iteration - # to not close the openssl s_client STDIN too early like on the HTTP case. - # See https://github.com/drwetter/testssl.sh/issues/2590 - ssl_reneg_attempts=2 + ssl_reneg_attempts=1 fi # We try again if server is HTTP. This could be either a node.js server or something else. # Mitigations (default values) for: @@ -17200,7 +17197,11 @@ run_renego() { # too early losing all the attempts before the session establishment as OpenSSL will not buffer them # (only the first will be till the establishement of the session). (j=0; while [[ $(grep -ac '^SSL-Session:' $TMPFILE) -ne 1 ]] && [[ $j -lt 30 ]]; do sleep $ssl_reneg_wait; ((j++)); done; \ - for ((i=0; i < ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \ + # Connection could be closed by the server with 0 return value. We do one more iteration to not close + # s_client STDIN too early as the close could come at any time and race with the tear down of s_client. + # See https://github.com/drwetter/testssl.sh/issues/2590 + # In this case the added iteration is harmfull as it will just spin in backgroup + for ((i=0; i <= ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \ # 0 means client is renegotiating & doesn't return an error --> vuln! # 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output # Exemption from above: server closed the connection but return value was zero @@ -17208,11 +17209,11 @@ run_renego() { while [[ $(grep -ac '^RENEGOTIATING' $ERRFILE) -ne $((i+1)) ]] && [[ -f $TEMPDIR/allowed_to_loop ]] \ && [[ $(tail -n1 $ERRFILE |grep -acE '^(RENEGOTIATING|depth|verify|notAfter)') -eq 1 ]] \ && [[ $k -lt 120 ]]; \ - do sleep $ssl_reneg_wait; ((k++)); if (tail -5 $TMPFILE| grep -qa '^closed'); then sleep 1; break; fi; done; \ + do sleep $ssl_reneg_wait; ((k++)); if (tail -5 $TMPFILE| grep -qa '^closed'); then break; fi; done; \ done) | \ $OPENSSL_NOTIMEOUT 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 & + ( sleep $((ssl_reneg_attempts*3+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, some target hang/block after some tries (some LiteSpeed servers don't answer at all on "R" and block). @@ -17220,8 +17221,10 @@ run_renego() { tmp_result=$? pkill -HUP -P $watcher wait $watcher + # Stop any backgroud wait loop rm -f $TEMPDIR/allowed_to_loop - # If we are here, we have done the loop + # If we are here, we have done the loop. Count the effective renego attempts. + # It could be less than the numbers of "for" itterations (minus one) in case of late server close. loop_reneg=$(grep -ac '^RENEGOTIATING' $ERRFILE) # As above, some servers close the connection and return value is zero if (tail -5 $TMPFILE| grep -qa '^closed'); then @@ -17239,7 +17242,7 @@ run_renego() { 1) prln_svrty_good "not vulnerable (OK)" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" ;; - 2) pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out ($((${ssl_reneg_attempts}*3))s)" # it hung + 2) pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out ($((${ssl_reneg_attempts}*3+3))s)" # it hung fileout "$jsonID" "OK" "likely not vulnerable (timed out)" "$cve" "$cwe" ;; *) prln_warning "FIXME (bug): $sec_client_renego" @@ -17256,7 +17259,7 @@ run_renego() { 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}*3))s(timeout))" + outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in $((${ssl_reneg_attempts}*3+3))s(timeout))" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" ;; *) prln_warning "FIXME (bug): $sec_client_renego ($ssl_reneg_attempts tries)" From d8b439e48c24b60855a92cb9dae5ea5455cccd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 4 Nov 2024 20:53:07 +0100 Subject: [PATCH 03/11] Address a theorically still possible non HTTP case --- testssl.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 1391175..6fc1069 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17200,7 +17200,7 @@ run_renego() { # Connection could be closed by the server with 0 return value. We do one more iteration to not close # s_client STDIN too early as the close could come at any time and race with the tear down of s_client. # See https://github.com/drwetter/testssl.sh/issues/2590 - # In this case the added iteration is harmfull as it will just spin in backgroup + # In this case the added iteration is harmless as it will just spin in backgroup for ((i=0; i <= ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \ # 0 means client is renegotiating & doesn't return an error --> vuln! # 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output @@ -17230,11 +17230,16 @@ run_renego() { if (tail -5 $TMPFILE| grep -qa '^closed'); then tmp_result=1 fi + # timeout reached ? if [[ -f $TEMPDIR/was_killed ]]; then tmp_result=2 rm -f $TEMPDIR/was_killed fi if [[ $SERVICE != HTTP ]]; then + # theoric possible case + if [[ $loop_reneg -eq 2 ]]; + $tmp_result=0 + fi case $tmp_result in 0) pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat" fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint" From 5773303f239e3e7be412b61582bd81029ee58fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 4 Nov 2024 20:59:45 +0100 Subject: [PATCH 04/11] Correct incomplete commit --- testssl.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index 6fc1069..986f4b8 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17237,8 +17237,8 @@ run_renego() { fi if [[ $SERVICE != HTTP ]]; then # theoric possible case - if [[ $loop_reneg -eq 2 ]]; - $tmp_result=0 + if [[ $loop_reneg -eq 2 ]]; then + tmp_result=0 fi case $tmp_result in 0) pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat" From 76254229761e9f224643ef58a7b719679903a62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 4 Nov 2024 21:02:03 +0100 Subject: [PATCH 05/11] Spell fix --- testssl.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index 986f4b8..775b7a3 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17221,10 +17221,10 @@ run_renego() { tmp_result=$? pkill -HUP -P $watcher wait $watcher - # Stop any backgroud wait loop + # Stop any background wait loop rm -f $TEMPDIR/allowed_to_loop # If we are here, we have done the loop. Count the effective renego attempts. - # It could be less than the numbers of "for" itterations (minus one) in case of late server close. + # It could be less than the numbers of "for" iterations (minus one) in case of late server close. loop_reneg=$(grep -ac '^RENEGOTIATING' $ERRFILE) # As above, some servers close the connection and return value is zero if (tail -5 $TMPFILE| grep -qa '^closed'); then From 1aaab67e81569cfbf0bf770cbbb65d571f9d5342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Tue, 5 Nov 2024 12:59:01 +0100 Subject: [PATCH 06/11] Multiple IP fix and simple not vulnerable printing case recover - Recover the "not vulnerable" case (no mitigation) printing, cosmetic fix. - With the removing of all s_client invocation other than the main loop one, fix the init of the ERRFILE and TMPFILE: no need to append, no need to remove, inconditionally zap the content before the loop. --- testssl.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/testssl.sh b/testssl.sh index 775b7a3..e3c63c4 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17172,8 +17172,6 @@ run_renego() { # We will need $ERRFILE for mitigation detection if [[ $ERRFILE =~ dev.null ]]; then ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE - # cleanup previous run if any (multiple IP) - rm -f $ERRFILE restore_errfile=1 else restore_errfile=0 @@ -17190,7 +17188,9 @@ run_renego() { # Amount of times tested before breaking is set in SSL_RENEG_ATTEMPTS. # Clear the log to not get the content of previous run before the execution of the new one. + # (Used in the loop tests before s_client invocation) echo -n > $TMPFILE + echo -n > $ERRFILE # RENEGOTIATING wait loop watchdog file touch $TEMPDIR/allowed_to_loop # If we dont wait for the session to be established on slow server, we will try to re-negotiate @@ -17211,7 +17211,7 @@ run_renego() { && [[ $k -lt 120 ]]; \ do sleep $ssl_reneg_wait; ((k++)); if (tail -5 $TMPFILE| grep -qa '^closed'); then break; fi; done; \ done) | \ - $OPENSSL_NOTIMEOUT s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE & + $OPENSSL_NOTIMEOUT s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE & pid=$! ( sleep $((ssl_reneg_attempts*3+3)) && kill $pid && touch $TEMPDIR/was_killed ) >&2 2>/dev/null & watcher=$! @@ -17235,6 +17235,9 @@ run_renego() { tmp_result=2 rm -f $TEMPDIR/was_killed fi + if [[ $tmp_result -eq 1 ]] && [[ loop_reneg -eq 1 ]]; then + tmp_result=3 + fi if [[ $SERVICE != HTTP ]]; then # theoric possible case if [[ $loop_reneg -eq 2 ]]; then @@ -17244,7 +17247,7 @@ run_renego() { 0) pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat" fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint" ;; - 1) prln_svrty_good "not vulnerable (OK)" + 1|3) prln_svrty_good "not vulnerable (OK)" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" ;; 2) pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out ($((${ssl_reneg_attempts}*3+3))s)" # it hung @@ -17263,6 +17266,9 @@ run_renego() { 1) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated (disconnect after $loop_reneg/$ssl_reneg_attempts attempts)" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" ;; + 3) prln_svrty_good "not vulnerable (OK)" + fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" + ;; 2) pr_svrty_good "not vulnerable (OK)"; \ outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in $((${ssl_reneg_attempts}*3+3))s(timeout))" fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" From 991c1fefb228f62f0506a0d9aef05307da6599df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Thu, 7 Nov 2024 12:25:50 +0100 Subject: [PATCH 07/11] One tab fix --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index e3c63c4..fe0b2db 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17188,7 +17188,7 @@ run_renego() { # Amount of times tested before breaking is set in SSL_RENEG_ATTEMPTS. # Clear the log to not get the content of previous run before the execution of the new one. - # (Used in the loop tests before s_client invocation) + # (Used in the loop tests before s_client invocation) echo -n > $TMPFILE echo -n > $ERRFILE # RENEGOTIATING wait loop watchdog file From e4e3afbbe8ed721e28cb11037950d2990d1b7a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 9 Dec 2024 10:46:45 +0100 Subject: [PATCH 08/11] Tentative to fix CI tests --- testssl.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testssl.sh b/testssl.sh index fe0b2db..0c6c668 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17169,6 +17169,10 @@ run_renego() { prln_warning "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" fileout "$jsonID" "WARN" "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" else + # We will extensively use subshell and command pipe + # Do not let herited pipeline error control interfere + [[ $- == *e* ]] && restore_pipeerror=1 + [[ $restore_pipeerror == 1 ]] && set +e # We will need $ERRFILE for mitigation detection if [[ $ERRFILE =~ dev.null ]]; then ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE @@ -17279,6 +17283,7 @@ run_renego() { ;; esac fi + [[ $restore_pipeerror == 1 ]] && set -e fi #pr_bold " Insecure Client-Initiated Renegotiation " # pre-RFC 5746, CVE-2009-3555 From 88856ecad5f6e83e127cf82675dd47e22150ba36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 9 Dec 2024 12:00:16 +0100 Subject: [PATCH 09/11] 2nd try --- testssl.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/testssl.sh b/testssl.sh index 0616064..089abd1 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17173,10 +17173,11 @@ run_renego() { prln_warning "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" fileout "$jsonID" "WARN" "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" else - # We will extensively use subshell and command pipe - # Do not let herited pipeline error control interfere - [[ $- == *e* ]] && restore_pipeerror=1 - [[ $restore_pipeerror == 1 ]] && set +e +# # We will extensively use subshell and command pipe +# # Do not let herited pipeline error control interfere +# [[ $- == *e* ]] && restore_pipeerror=1 +# [[ $restore_pipeerror == 1 ]] && set +e +# set +o pipefail # We will need $ERRFILE for mitigation detection if [[ $ERRFILE =~ dev.null ]]; then ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE @@ -17209,7 +17210,7 @@ run_renego() { # s_client STDIN too early as the close could come at any time and race with the tear down of s_client. # See https://github.com/drwetter/testssl.sh/issues/2590 # In this case the added iteration is harmless as it will just spin in backgroup - for ((i=0; i <= ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \ + for ((i=0; i <= ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R 2>/dev/null; k=0; \ # 0 means client is renegotiating & doesn't return an error --> vuln! # 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output # Exemption from above: server closed the connection but return value was zero @@ -17287,7 +17288,7 @@ run_renego() { ;; esac fi - [[ $restore_pipeerror == 1 ]] && set -e +# [[ $restore_pipeerror == 1 ]] && set -e fi #pr_bold " Insecure Client-Initiated Renegotiation " # pre-RFC 5746, CVE-2009-3555 From 6c17b6641873636433ed15ab03b148b3307278e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fust=C3=A9?= Date: Mon, 9 Dec 2024 14:19:56 +0100 Subject: [PATCH 10/11] CI fix : Cleanup testssl.sh worked as expected. Under the hood, broken pipes are expected as part of the fast loop exit strategy that relies as little as possible on timeout detection. But under the CI, testssl.sh output is garbled by the subshells stderr outputs, catched for some reason by 'prove -v'. Simply redirecting the stderr output of the offending command to /dev/null fixes the problem. --- testssl.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/testssl.sh b/testssl.sh index 089abd1..7983aae 100755 --- a/testssl.sh +++ b/testssl.sh @@ -17173,11 +17173,6 @@ run_renego() { prln_warning "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" fileout "$jsonID" "WARN" "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" else -# # We will extensively use subshell and command pipe -# # Do not let herited pipeline error control interfere -# [[ $- == *e* ]] && restore_pipeerror=1 -# [[ $restore_pipeerror == 1 ]] && set +e -# set +o pipefail # We will need $ERRFILE for mitigation detection if [[ $ERRFILE =~ dev.null ]]; then ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE @@ -17288,7 +17283,6 @@ run_renego() { ;; esac fi -# [[ $restore_pipeerror == 1 ]] && set -e fi #pr_bold " Insecure Client-Initiated Renegotiation " # pre-RFC 5746, CVE-2009-3555 From 002b91192c630853f74f47a75126e1ec8dcc08d0 Mon Sep 17 00:00:00 2001 From: Dirk Date: Fri, 24 Jan 2025 13:50:35 +0100 Subject: [PATCH 11/11] fix spelling --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 3163425..2a37707 100755 --- a/testssl.sh +++ b/testssl.sh @@ -248,7 +248,7 @@ OPENSSL2=${OPENSSL2:-/usr/bin/openssl} # This will be openssl version >=1.1.1 ( OPENSSL2_HAS_TLS_1_3=false # If we run with supplied binary AND $OPENSSL2 supports TLS 1.3 this will be set to true OSSL_SHORTCUT=${OSSL_SHORTCUT:-true} # If you don't want automagically switch from $OPENSSL to $OPENSSL2 for TLS 1.3-only hosts, set this to false OPENSSL_LOCATION="" -OPENSSL_NOTIMEOUT="" # Needed for renogitiation tests +OPENSSL_NOTIMEOUT="" # Needed for renegotiation tests IKNOW_FNAME=false FIRST_FINDING=true # is this the first finding we are outputting to file? JSONHEADER=true # include JSON headers and footers in HTML file, if one is being created