Skip to content

Commit dab177f

Browse files
committed
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
1 parent 245ad2a commit dab177f

1 file changed

Lines changed: 87 additions & 94 deletions

File tree

testssl.sh

Lines changed: 87 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -17076,7 +17076,7 @@ run_ticketbleed() {
1707617076
#
1707717077
run_renego() {
1707817078
local legacycmd="" proto="$OPTIMAL_PROTO"
17079-
local sec_renego sec_client_renego
17079+
local sec_renego
1708017080
local -i ret=0
1708117081
local cve=""
1708217082
local cwe="CWE-310"
@@ -17168,7 +17168,6 @@ run_renego() {
1716817168
elif [[ "$CLIENT_AUTH" == required ]] && [[ -z "$MTLS" ]]; then
1716917169
prln_warning "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested"
1717017170
fileout "$jsonID" "WARN" "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested"
17171-
sec_client_renego=1
1717217171
else
1717317172
# We will need $ERRFILE for mitigation detection
1717417173
if [[ $ERRFILE =~ dev.null ]]; then
@@ -17179,99 +17178,91 @@ run_renego() {
1717917178
else
1718017179
restore_errfile=0
1718117180
fi
17182-
# We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background
17183-
# msg enables us to look deeper into it while debugging
17184-
echo R | $OPENSSL s_client $(s_client_options "$proto $BUGS $legacycmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE &
17185-
wait_kill $! $HEADER_MAXSLEEP
17186-
if [[ $? -eq 3 ]]; then
17187-
pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out" # it hung
17188-
fileout "$jsonID" "OK" "likely not vulnerable (timed out)" "$cve" "$cwe"
17189-
sec_client_renego=1
17181+
if [[ $SERVICE != HTTP ]]; then
17182+
# Connection could be closed by the server after one try so we will try two iteration
17183+
# to not close the openssl s_client STDIN too early like on the HTTP case.
17184+
# See https://github.com/drwetter/testssl.sh/issues/2590
17185+
ssl_reneg_attempts=2
17186+
fi
17187+
# We try again if server is HTTP. This could be either a node.js server or something else.
17188+
# Mitigations (default values) for:
17189+
# - node.js allows 3x R and then blocks. So then 4x should be tested.
17190+
# - F5 BIG-IP ADS allows 5x R and then blocks. So then 6x should be tested.
17191+
# - Stormshield allows 9x and then blocks. So then 10x should be tested.
17192+
# This way we save a couple seconds as we weeded out the ones which are more robust
17193+
# Amount of times tested before breaking is set in SSL_RENEG_ATTEMPTS.
17194+
17195+
# Clear the log to not get the content of previous run before the execution of the new one.
17196+
echo -n > $TMPFILE
17197+
# RENEGOTIATING wait loop watchdog file
17198+
touch $TEMPDIR/allowed_to_loop
17199+
# If we dont wait for the session to be established on slow server, we will try to re-negotiate
17200+
# too early losing all the attempts before the session establishment as OpenSSL will not buffer them
17201+
# (only the first will be till the establishement of the session).
17202+
(j=0; while [[ $(grep -ac '^SSL-Session:' $TMPFILE) -ne 1 ]] && [[ $j -lt 30 ]]; do sleep $ssl_reneg_wait; ((j++)); done; \
17203+
for ((i=0; i < ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \
17204+
# 0 means client is renegotiating & doesn't return an error --> vuln!
17205+
# 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output
17206+
# Exemption from above: server closed the connection but return value was zero
17207+
# See https://github.com/drwetter/testssl.sh/issues/1725 and referenced issue @haproxy
17208+
while [[ $(grep -ac '^RENEGOTIATING' $ERRFILE) -ne $((i+1)) ]] && [[ -f $TEMPDIR/allowed_to_loop ]] \
17209+
&& [[ $(tail -n1 $ERRFILE |grep -acE '^(RENEGOTIATING|depth|verify|notAfter)') -eq 1 ]] \
17210+
&& [[ $k -lt 120 ]]; \
17211+
do sleep $ssl_reneg_wait; ((k++)); if (tail -5 $TMPFILE| grep -qa '^closed'); then sleep 1; break; fi; done; \
17212+
done) | \
17213+
$OPENSSL_NOTIMEOUT s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE &
17214+
pid=$!
17215+
( sleep $((ssl_reneg_attempts*3)) && kill $pid && touch $TEMPDIR/was_killed ) >&2 2>/dev/null &
17216+
watcher=$!
17217+
# Trick to get the return value of the openssl command, output redirection and a timeout.
17218+
# Yes, some target hang/block after some tries (some LiteSpeed servers don't answer at all on "R" and block).
17219+
wait $pid
17220+
tmp_result=$?
17221+
pkill -HUP -P $watcher
17222+
wait $watcher
17223+
rm -f $TEMPDIR/allowed_to_loop
17224+
# If we are here, we have done the loop
17225+
loop_reneg=$(grep -ac '^RENEGOTIATING' $ERRFILE)
17226+
# As above, some servers close the connection and return value is zero
17227+
if (tail -5 $TMPFILE| grep -qa '^closed'); then
17228+
tmp_result=1
17229+
fi
17230+
if [[ -f $TEMPDIR/was_killed ]]; then
17231+
tmp_result=2
17232+
rm -f $TEMPDIR/was_killed
17233+
fi
17234+
if [[ $SERVICE != HTTP ]]; then
17235+
case $tmp_result in
17236+
0) pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat"
17237+
fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint"
17238+
;;
17239+
1) prln_svrty_good "not vulnerable (OK)"
17240+
fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
17241+
;;
17242+
2) pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out ($((${ssl_reneg_attempts}*3))s)" # it hung
17243+
fileout "$jsonID" "OK" "likely not vulnerable (timed out)" "$cve" "$cwe"
17244+
;;
17245+
*) prln_warning "FIXME (bug): $sec_client_renego"
17246+
fileout "$jsonID" "DEBUG" "FIXME (bug) $sec_client_renego - Please report" "$cve" "$cwe"
17247+
ret=1
17248+
;;
17249+
esac
1719017250
else
17191-
# second try in the foreground as we are sure now it won't hang
17192-
echo R | $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE
17193-
sec_client_renego=$?
17194-
# 0 means client is renegotiating & doesn't return an error --> vuln!
17195-
# 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output
17196-
if tail -5 $TMPFILE| grep -qa '^closed'; then
17197-
# Exemption from above: server closed the connection but return value was zero
17198-
# See https://github.com/drwetter/testssl.sh/issues/1725 and referenced issue @haproxy
17199-
sec_client_renego=1
17200-
fi
17201-
case "$sec_client_renego" in
17202-
0) # We try again if server is HTTP. This could be either a node.js server or something else.
17203-
# Mitigations (default values) for:
17204-
# - node.js allows 3x R and then blocks. So then 4x should be tested.
17205-
# - F5 BIG-IP ADS allows 5x R and then blocks. So then 6x should be tested.
17206-
# - Stormshield allows 9x and then blocks. So then 10x should be tested.
17207-
# This way we save a couple seconds as we weeded out the ones which are more robust
17208-
# Amount of times tested before breaking is set in SSL_RENEG_ATTEMPTS.
17209-
if [[ $SERVICE != HTTP ]]; then
17210-
pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat"
17211-
fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint"
17212-
else
17213-
# Clear the log to not get the content of previous run before the execution of the new one.
17214-
echo -n > $TMPFILE
17215-
#RENEGOTIATING wait loop watchdog file
17216-
touch $TEMPDIR/allowed_to_loop
17217-
# If we dont wait for the session to be established on slow server, we will try to re-negotiate
17218-
# too early losing all the attempts before the session establishment as OpenSSL will not buffer them
17219-
# (only the first will be till the establishement of the session).
17220-
(j=0; while [[ $(grep -ac '^SSL-Session:' $TMPFILE) -ne 1 ]] && [[ $j -lt 30 ]]; do sleep $ssl_reneg_wait; ((j++)); done; \
17221-
for ((i=0; i < ssl_reneg_attempts; i++ )); do sleep $ssl_reneg_wait; echo R; k=0; \
17222-
while [[ $(grep -ac '^RENEGOTIATING' $ERRFILE) -ne $((i+3)) ]] && [[ -f $TEMPDIR/allowed_to_loop ]] \
17223-
&& [[ $(tail -n1 $ERRFILE |grep -acE '^(RENEGOTIATING|depth|verify|notAfter)') -eq 1 ]] \
17224-
&& [[ $k -lt 120 ]]; \
17225-
do sleep $ssl_reneg_wait; ((k++)); if (tail -5 $TMPFILE| grep -qa '^closed'); then sleep 1; break; fi; done; \
17226-
done) | \
17227-
$OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE &
17228-
pid=$!
17229-
( sleep $((ssl_reneg_attempts*3)) && kill $pid && touch $TEMPDIR/was_killed ) >&2 2>/dev/null &
17230-
watcher=$!
17231-
# Trick to get the return value of the openssl command, output redirection and a timeout.
17232-
# Yes, some target hang/block after some tries.
17233-
wait $pid
17234-
tmp_result=$?
17235-
pkill -HUP -P $watcher
17236-
wait $watcher
17237-
rm -f $TEMPDIR/allowed_to_loop
17238-
# If we are here, we have done two successful renegotiation (-2) and do the loop
17239-
loop_reneg=$(($(grep -ac '^RENEGOTIATING' $ERRFILE)-2))
17240-
# As above, some servers close the connection and return value is zero
17241-
if (tail -5 $TMPFILE| grep -qa '^closed'); then
17242-
tmp_result=1
17243-
fi
17244-
if [[ -f $TEMPDIR/was_killed ]]; then
17245-
tmp_result=2
17246-
rm -f $TEMPDIR/was_killed
17247-
fi
17248-
case $tmp_result in
17249-
0) pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat ($ssl_reneg_attempts attempts)"
17250-
fileout "$jsonID" "HIGH" "VULNERABLE, DoS threat" "$cve" "$cwe" "$hint"
17251-
;;
17252-
1) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated (disconnect after $loop_reneg/$ssl_reneg_attempts attempts)"
17253-
fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe"
17254-
;;
17255-
2) pr_svrty_good "not vulnerable (OK)"; \
17256-
outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in $((${ssl_reneg_attempts}*3))s(timeout))"
17257-
fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe"
17258-
;;
17259-
*) prln_warning "FIXME (bug): $sec_client_renego ($ssl_reneg_attempts tries)"
17260-
fileout "$jsonID" "DEBUG" "FIXME (bug $ssl_reneg_attempts tries) $sec_client_renego" "$cve" "$cwe"
17261-
ret=1
17262-
;;
17263-
esac
17264-
fi
17265-
;;
17266-
1)
17267-
prln_svrty_good "not vulnerable (OK)"
17268-
fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
17269-
;;
17270-
*)
17271-
prln_warning "FIXME (bug): $sec_client_renego"
17272-
fileout "$jsonID" "DEBUG" "FIXME (bug) $sec_client_renego - Please report" "$cve" "$cwe"
17273-
ret=1
17274-
;;
17251+
case $tmp_result in
17252+
0) pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat ($ssl_reneg_attempts attempts)"
17253+
fileout "$jsonID" "HIGH" "VULNERABLE, DoS threat" "$cve" "$cwe" "$hint"
17254+
;;
17255+
1) pr_svrty_good "not vulnerable (OK)"; outln " -- mitigated (disconnect after $loop_reneg/$ssl_reneg_attempts attempts)"
17256+
fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe"
17257+
;;
17258+
2) pr_svrty_good "not vulnerable (OK)"; \
17259+
outln " -- mitigated ($loop_reneg successful reneg within ${ssl_reneg_attempts} in $((${ssl_reneg_attempts}*3))s(timeout))"
17260+
fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe"
17261+
;;
17262+
*) prln_warning "FIXME (bug): $sec_client_renego ($ssl_reneg_attempts tries)"
17263+
fileout "$jsonID" "DEBUG" "FIXME (bug $ssl_reneg_attempts tries) $sec_client_renego" "$cve" "$cwe"
17264+
ret=1
17265+
;;
1727517266
esac
1727617267
fi
1727717268
fi
@@ -20447,6 +20438,8 @@ find_openssl_binary() {
2044720438
fi
2044820439
fi
2044920440

20441+
OPENSSL_NOTIMEOUT=$OPENSSL
20442+
2045020443
if ! "$do_mass_testing"; then
2045120444
if [[ -n $OPENSSL_TIMEOUT ]]; then
2045220445
OPENSSL="$TIMEOUT_CMD $OPENSSL_TIMEOUT $OPENSSL"

0 commit comments

Comments
 (0)