From 0d8abd131efcf12d90d4ce9aea4cb479b4e210bd Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 6 Mar 2020 19:25:44 +0100 Subject: [PATCH 1/7] Modernize code2network() This function had before a mixture of sed and tr commands which was now replaced by bash internal functions. It makes the code better, performance gain in the LAN is neglectable (1s). This brings code2network somewhat in line with socksend(). This function does basically the same (and thus is probably prone to extinction ;-) ). Albeit there the good thing is it does conversion and sending in one shot. --- testssl.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/testssl.sh b/testssl.sh index 48e0da448..9788cf2f4 100755 --- a/testssl.sh +++ b/testssl.sh @@ -10462,8 +10462,18 @@ send_close_notify() { # Format string properly for socket # ARG1: any commented sequence of two bytes hex, separated by commas. It can contain comments, new lines, tabs and white spaces # NW_STR holds the global with the string prepared for printf, like '\x16\x03\x03\' +# As opposed to socksend() below this is the function where the hexbytes are NOT preceeded by x (mainly they are @ heartbleed, +# ccs and # ticketbleed). Best would be to settle on one function and remove the need for NW_STR, i.e. do everything in one shot. code2network() { - NW_STR=$(sed -e 's/,/\\\x/g' <<< "$1" | sed -e 's/# .*$//g' -e 's/ //g' -e '/^$/d' | tr -d '\n' | tr -d '\t') + local temp="" line="" + + NW_STR=$(while read -r line; do + [[ -z "$line" ]] && continue # blank line + temp="${line%%\#*}" # remove comments + temp="${temp//,/\\\x}" # comma to \x + temp="${temp//[\t ]/}" # blank and tabs + printf "%s" "$temp" + done <<< "$1") } # sockets inspired by http://blog.chris007.de/?p=238 @@ -14708,7 +14718,7 @@ tls_sockets() { finished_msg="$(sym-encrypt "$cipher" "$key" "$(get-nonce "$iv" 0)" "${finished_msg}16" "")" fi finished_msg="$aad$finished_msg" - + len=${#finished_msg} for (( i=0; i < len; i+=2 )); do data+=", ${finished_msg:i:2}" @@ -14716,7 +14726,7 @@ tls_sockets() { debugme echo -e "\nsending finished..." socksend_clienthello "${data}" sleep $USLEEP_SND - + # Compute application traffic keys and IVs. master_secret="$(derive-master-secret "$cipher" "$handshake_secret")" master_traffic_keys="$(derive-application-traffic-keys "$cipher" "$master_secret" "$msg_transcript" server)" From 6a7bf1674c04b83247d7451338e9a25630e45e72 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 6 Mar 2020 21:31:23 +0100 Subject: [PATCH 2/7] Move more towards a common socksend* function This commit chamges a few functions / calls so that the hexbyte syntax with leading x was changed to one without. The calls then need to change from socksend --> socksend_clienthello . The goal is basically to remove socksend() at some point. Also socksend_clienthello()'s use of NW_STR should be reconsidered. This PR removes also some blanks, at the right hand side of some double square brackets and at some empty lines --- testssl.sh | 151 +++++++++++++++++++++++++++-------------------------- 1 file changed, 76 insertions(+), 75 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9788cf2f4..40322fb93 100755 --- a/testssl.sh +++ b/testssl.sh @@ -10300,8 +10300,8 @@ starttls_nntp_dialog() { starttls_postgres_dialog() { debugme echo "=== starting postgres STARTTLS dialog ===" - local init_tls=", x00, x00 ,x00 ,x08 ,x04 ,xD2 ,x16 ,x2F" - socksend "${init_tls}" 0 && debugme echo "initiated STARTTLS" && + local init_tls=", 00, 00 ,00 ,08 ,04 ,D2 ,16 ,2F" + socksend_clienthello "${init_tls}" 0 && debugme echo "initiated STARTTLS" && starttls_io "" S 1 && debugme echo "received ack (="S") for STARTTLS" local ret=$? debugme echo "=== finished postgres STARTTLS dialog with ${ret} ===" @@ -10311,14 +10311,14 @@ starttls_postgres_dialog() { starttls_mysql_dialog() { debugme echo "=== starting mysql STARTTLS dialog ===" local login_request=" - , x20, x00, x00, x01, # payload_length, sequence_id - x85, xae, xff, x00, # capability flags, CLIENT_SSL always set - x00, x00, x00, x01, # max-packet size - x21, # character set - x00, x00, x00, x00, x00, x00, x00, x00, # string[23] reserved (all [0]) - x00, x00, x00, x00, x00, x00, x00, x00, - x00, x00, x00, x00, x00, x00, x00" - socksend "${login_request}" 0 + , 20, 00, 00, 01, # payload_length, sequence_id + 85, ae, ff, 00, # capability flags, CLIENT_SSL always set + 00, 00, 00, 01, # max-packet size + 21, # character set + 00, 00, 00, 00, 00, 00, 00, 00, # string[23] reserved (all [0]) + 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00" + socksend_clienthello "${login_request}" 0 starttls_just_read 1 && debugme echo "read succeeded" # 1 is the timeout value which only MySQL needs. Note, there seems no response whether STARTTLS # succeeded. We could try harder, see https://github.com/openssl/openssl/blob/master/apps/s_client.c @@ -10453,9 +10453,9 @@ send_close_notify() { debugme echo "sending close_notify..." if [[ $detected_tlsversion == 0300 ]]; then - socksend ",x15, x03, x00, x00, x02, x02, x00" 0 + socksend_clienthello ",15, 03, 00, 00, 02, 02, 00" 0 else - socksend ",x15, x03, x01, x00, x02, x02, x00" 0 + socksend_clienthello ",15, 03, 01, 00, 02, 02, 00" 0 fi } @@ -10467,13 +10467,13 @@ send_close_notify() { code2network() { local temp="" line="" - NW_STR=$(while read -r line; do + NW_STR="$(while read -r line; do [[ -z "$line" ]] && continue # blank line temp="${line%%\#*}" # remove comments temp="${temp//,/\\\x}" # comma to \x temp="${temp//[\t ]/}" # blank and tabs printf "%s" "$temp" - done <<< "$1") + done <<< "$1")" } # sockets inspired by http://blog.chris007.de/?p=238 @@ -10497,6 +10497,7 @@ socksend_clienthello() { # ARG1: hexbytes -- preceeded by x -- separated by commas, with a leading comma # ARG2: seconds to sleep +#FIXME: use socksend_clienthello instead. This will be removed soon!! socksend() { local data line @@ -14516,7 +14517,7 @@ resend_if_hello_retry_request() { if [[ "$server_version" == 0304 ]] || [[ 0x$server_version -ge 0x7f16 ]]; then # Send a dummy change cipher spec for middlebox compatibility. debugme echo -en "\nsending dummy change cipher spec... " - socksend ", x14, x03, x03 ,x00, x01, x01" 0 + socksend_clienthello ", 14, 03, 03 ,00, 01, 01" 0 fi debugme echo -en "\nsending second client hello... " second_clienthello="$(modify_clienthello "$original_clienthello" "$new_key_share" "$cookie")" @@ -14824,7 +14825,7 @@ receive_app_data() { read -r tls_version cipher server_key server_iv server_seq client_key client_iv client_seq <<< "$APP_TRAF_KEY_INFO" [[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -lt 25 ]] && include_headers=false - + sleep $USLEEP_REC while true; do len=${#ciphertext} @@ -14906,29 +14907,30 @@ run_heartbleed(){ fi if [[ 0 -eq $(has_server_protocol tls1) ]]; then - tls_hexcode="x03, x01" + tls_hexcode="03,01" elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then - tls_hexcode="x03, x02" + tls_hexcode="03,02" elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then - tls_hexcode="x03, x03" + tls_hexcode="03,03" elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then - tls_hexcode="x03, x00" + tls_hexcode="03,00" else # no protocol for some reason defined, determine TLS versions offered with a new handshake $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE /dev/null | \ hexdump -v -e '16/1 "%02x"')" if [[ -z "$encrypted_pms" ]]; then - if [[ "$DETECTED_TLS_VERSION" == "0300" ]]; then - socksend ",x15, x03, x00, x00, x02, x02, x00" 0 + if [[ "$DETECTED_TLS_VERSION" == 0300 ]]; then + socksend_clienthello ",15, 03, 00, 00, 02, 02, 00" 0 else - socksend ",x15, x03, x01, x00, x02, x02, x00" 0 + socksend_clienthello ",15, 03, 01, 00, 02, 02, 00" 0 fi close_socket prln_fixme "Conversion of public key failed around line $((LINENO - 9))" From 8dbaab36569f9faf98e25d1b7cd5121521a234f9 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Sat, 7 Mar 2020 15:40:19 +0100 Subject: [PATCH 3/7] Socksend modernize part 3, with a PoC for #1535: DONT USE THIS OTHERWISE This moves the run_ticketbleed function to the socketsend_clienthello. It is not working yet, see also #1535 why. This is just for the PoC, I'll explain: It has now a function named check_bytestream() which will be called in debug mode 1 and checks whether the byte stream to be send via bash sockets is properly formatted. It can detect bugs which otherwise would be hard to discover. DO NOT USE IT for anything else than the check ---snip: code: check_bytestream() { local line="" local -i i=0 # We do a search and replace so that \xaa\x29 becomes # _xaa # _x29 # # "echo -e" helps us to get a multiline string while read -r line; do if [[ $i -eq 0 ]]; then # first line is empty because this is a LF : elif [[ ${#line} -ne 4 ]] && [[ $i != 0 ]]; then echo "length of byte $i called from $2 is not ok" elif [[ ${line:0:1} != _ ]]; then echo "char $i called from $2 doesn't start with a \"\\\"" elif [[ ${line:1:1} != x ]]; then echo "char $i called from $2 doesn't have an x in second position" elif [[ ${line:2:2} != [0-9a-fA-F][0-9a-fA-F] ]]; then echo "byte $i called from $2 is not hex" fi i+=1 done < <( echo -e ${1//\\/\\n_}) } socksend_clienthello() { local data="" code2network "$1" data="$NW_STR" if [[ "$DEBUG" -ge 1 ]]; then check_bytestream "$data" "${FUNCNAME[1]}" [[ "$DEBUG" -ge 4 ]] && echo && echo "\"$data\"" [..] Result (./testssl.sh -q --debug=1 -U dev.testssl.sh): Testing vulnerabilities Heartbleed (CVE-2014-0160) not vulnerable (OK), no heartbeat extension CCS (CVE-2014-0224) not vulnerable (OK) Ticketbleed (CVE-2016-9244), experiment. length of byte 311 called from run_ticketbleed is not ok length of byte 312 called from run_ticketbleed is not ok length of byte 313 called from run_ticketbleed is not ok length of byte 314 called from run_ticketbleed is not ok length of byte 315 called from run_ticketbleed is not ok length of byte 316 called from run_ticketbleed is not ok length of byte 317 called from run_ticketbleed is not ok [..] ---snap Besides that: * dec02hex was corrected (only being used for run_ticketbleed) * dec04hex is still buggy and part of the problem * some quotes removed from rhs of [[]] --- testssl.sh | 189 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 113 insertions(+), 76 deletions(-) diff --git a/testssl.sh b/testssl.sh index 40322fb93..62422927a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -747,8 +747,9 @@ debugme() { return 0 } -hex2dec() { - echo $((16#$1)) +debugme1() { + [[ "$DEBUG" -ge 1 ]] && "$@" + return 0 } # convert 414243 into ABC @@ -760,15 +761,20 @@ hex2ascii() { done } +hex2dec() { + echo $((16#$1)) +} + + # convert decimal number < 256 to hex dec02hex() { - printf "x%02x" "$1" + printf "%02x" "$1" } # convert decimal number between 256 and < 256*256 to hex dec04hex() { local a=$(printf "%04x" "$1") - printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}" + printf "%02s, %02s" "${a:0:2}" "${a:2:2}" } @@ -10476,6 +10482,34 @@ code2network() { done <<< "$1")" } +# arg1: formatted bytesstream to be send +# arg2: caller function +check_bytestream() { + local line="" + local -i i=0 + + # We do a search and replace so that \xaa\x29 becomes + # _xaa + # _x29 + # + # "echo -e" helps us to get a multiline string + while read -r line; do + if [[ $i -eq 0 ]]; then + # first line is empty because this is a LF + : + elif [[ ${#line} -ne 4 ]] && [[ $i != 0 ]]; then + echo "length of byte $i called from $2 is not ok" + elif [[ ${line:0:1} != _ ]]; then + echo "char $i called from $2 doesn't start with a \"\\\"" + elif [[ ${line:1:1} != x ]]; then + echo "char $i called from $2 doesn't have an x in second position" + elif [[ ${line:2:2} != [0-9a-fA-F][0-9a-fA-F] ]]; then + echo "byte $i called from $2 is not hex" + fi + i+=1 + done < <( echo -e ${1//\\/\\n_}) +} + # sockets inspired by http://blog.chris007.de/?p=238 # ARG1: hexbytes separated by commas, with a leading comma # ARG2: seconds to sleep @@ -10484,7 +10518,10 @@ socksend_clienthello() { code2network "$1" data="$NW_STR" - [[ "$DEBUG" -ge 4 ]] && echo && echo "\"$data\"" + if [[ "$DEBUG" -ge 1 ]]; then + check_bytestream "$data" "${FUNCNAME[1]}" + [[ "$DEBUG" -ge 4 ]] && echo && echo "\"$data\"" + fi if [[ -z "$PRINTF" ]] ;then # We could also use "dd ibs=1M obs=1M" here but is seems to be at max 3% slower printf -- "$data" | cat >&5 2>/dev/null & @@ -10501,7 +10538,6 @@ socksend_clienthello() { socksend() { local data line - # read line per line and strip comments (bash internal func can't handle multiline statements data="$(while read line; do printf "${line%%\#*}" done <<< "$1" )" @@ -14880,7 +14916,7 @@ receive_app_data() { # mainly adapted from https://gist.github.com/takeshixx/10107280 # run_heartbleed(){ - local tls_hexcode + local tls_hexcode tls_proto local heartbleed_payload local -i n lines_returned local append="" @@ -14925,8 +14961,8 @@ run_heartbleed(){ fi debugme echo "using protocol $tls_hexcode" - # attention, this is dangerous as it relies on spaces etc. above - tls_sockets "${tls_hexcode:4:2}" "" "ephemeralkey" "" "" "false" + tls_proto="${tls_hexcode:4:2}" + tls_sockets "${tls_proto}" "" "ephemeralkey" "" "" "false" [[ $DEBUG -ge 4 ]] && tmln_out "\nsending payload with TLS version $tls_hexcode:" heartbleed_payload=", 18, $tls_hexcode, 00, 03, 01, 40,00" @@ -15187,8 +15223,8 @@ sub_session_ticket_tls() { run_ticketbleed() { local session_tckt_tls="" local -i len_ch=300 # fixed len of prepared clienthello below - local sid="x00,x0B,xAD,xC0,xDE,x00," # some abitratry bytes - local len_sid="$(( ${#sid} / 4))" + local sid="00,0B,AD,C0,DE,00," # some abitratry bytes + local len_sid="$(( ${#sid} / 3))" local xlen_sid="$(dec02hex $len_sid)" local -i len_tckt_tls=0 nr_sid_detected=0 local xlen_tckt_tls="" xlen_handshake_record_layer="" xlen_handshake_ssl_layer="" @@ -15214,7 +15250,7 @@ run_ticketbleed() { # highly unlikely that it is NOT supported. We may loose time here but it's more solid [[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions - if [[ ! "${TLS_EXTENSIONS}" =~ "session ticket" ]]; then + if [[ ! "${TLS_EXTENSIONS}" =~ session\ ticket ]]; then pr_svrty_best "not vulnerable (OK)" outln ", no session ticket extension" fileout "$jsonID" "OK" "no session ticket extension" "$cve" "$cwe" @@ -15222,26 +15258,26 @@ run_ticketbleed() { fi if [[ 0 -eq $(has_server_protocol tls1) ]]; then - tls_hexcode="x03, x01" + tls_hexcode="03,01" elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then - tls_hexcode="x03, x02" + tls_hexcode="03,02" elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then - tls_hexcode="x03, x03" + tls_hexcode="03,03" elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then - tls_hexcode="x03, x00" + tls_hexcode="03,00" else # no protocol for some reason defined, determine TLS versions offered with a new handshake $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE Date: Sun, 8 Mar 2020 16:41:41 +0100 Subject: [PATCH 4/7] Unit test for baseline sanity check of the ClientHello --- t/03_socketsend_OK.t | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 t/03_socketsend_OK.t diff --git a/t/03_socketsend_OK.t b/t/03_socketsend_OK.t new file mode 100755 index 000000000..fe6d15638 --- /dev/null +++ b/t/03_socketsend_OK.t @@ -0,0 +1,34 @@ +#!/usr/bin/env perl + +use strict; +use Test::More; + +my $tests = 0; +my $prg="./testssl.sh"; +my $check2run ="--ip=one --debug=1 -q --color 0"; +my $uri="dev.testssl.sh"; +my $out=""; +# Blacklists we use to trigger an error: +my $socket_regex_error1='length of byte .* called from .* is not ok'; +my $socket_regex_error2='char .* called from .* doesn\'t start with a '; +my $socket_regex_error3='char .* called from .* doesn\'t have an x in second position'; +my $socket_regex_error4='byte .* called from .* is not hex'; + +die "Unable to open $prg" unless -f $prg; + +printf "\n%s\n", "Unit test to verify socket byte stream is properly formatted --> $uri ..."; + +$out = `$prg $check2run $uri 2>&1`; +unlike($out, qr/$socket_regex_error1/, "check: \"$socket_regex_error1\""); +$tests++; +unlike($out, qr/$socket_regex_error2/, "check: \"$socket_regex_error2\""); +$tests++; +unlike($out, qr/$socket_regex_error3/, "check: \"$socket_regex_error3\""); +$tests++; +unlike($out, qr/$socket_regex_error4/, "check: \"$socket_regex_error4\""); +$tests++; + +printf "\n"; +done_testing($tests); + + From d1cec5ecd27f671c4032354afe5f19e5cb275ed2 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 9 Mar 2020 14:03:26 +0100 Subject: [PATCH 5/7] Fix ticketbleed This commit fixes ticketbleed so that using socksend_clienthello(). can being used. The function for retrieving the TLS session ticket is now using SNI and it was renamed to session_ticket_from_openssl() so that this can be used elsewhere. Also for the sake of better programming it is using bash only. In order to ease stripping whitespaces the bash option "extglob" was IN GENERAL set. This should only add the possibility to do extended pattern matching when using round brackets: ?(pattern-list) Matches zero or one occurrence of the given patterns. *(pattern-list) Matches zero or more occurrences of the given patterns. +(pattern-list) Matches one or more occurrences of the given patterns. @(pattern-list) Matches one of the given patterns. !(pattern-list) Matches anything except one of the given patterns. ... see bash(1). The man page though warns "separate matches against shorter strings, or using arrays of strings instead of a single long string, may be faster.". So when using ~100x we should do s.th. else. It also works under bashv3. The check_bytestream() function which was previously introduced now also list the offending string. --- testssl.sh | 64 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/testssl.sh b/testssl.sh index 62422927a..e978f3e31 100755 --- a/testssl.sh +++ b/testssl.sh @@ -67,6 +67,9 @@ # #################### Stop talking, action now #################### +# This is quite handy. It enables extended pattern matching operators (see bash(1). Works also for bashv3) +shopt -s extglob + ########### Definition of error codes # @@ -774,7 +777,7 @@ dec02hex() { # convert decimal number between 256 and < 256*256 to hex dec04hex() { local a=$(printf "%04x" "$1") - printf "%02s, %02s" "${a:0:2}" "${a:2:2}" + printf "%02s,%02s" "${a:0:2}" "${a:2:2}" } @@ -10457,7 +10460,7 @@ close_socket(){ send_close_notify() { local detected_tlsversion="$1" - debugme echo "sending close_notify..." + debugme echo " sending close_notify..." if [[ $detected_tlsversion == 0300 ]]; then socksend_clienthello ",15, 03, 00, 00, 02, 02, 00" 0 else @@ -10498,13 +10501,13 @@ check_bytestream() { # first line is empty because this is a LF : elif [[ ${#line} -ne 4 ]] && [[ $i != 0 ]]; then - echo "length of byte $i called from $2 is not ok" + echo "length of byte $i (${line/_/}) called from $2 is not ok" elif [[ ${line:0:1} != _ ]]; then - echo "char $i called from $2 doesn't start with a \"\\\"" + echo "char $i (${line/_/}) called from $2 doesn't start with a \"\\\"" elif [[ ${line:1:1} != x ]]; then - echo "char $i called from $2 doesn't have an x in second position" + echo "char $i (${line/_/}) called from $2 doesn't have an x in second position" elif [[ ${line:2:2} != [0-9a-fA-F][0-9a-fA-F] ]]; then - echo "byte $i called from $2 is not hex" + echo "byte $i (${line/_/}) called from $2 is not hex" fi i+=1 done < <( echo -e ${1//\\/\\n_}) @@ -15205,17 +15208,40 @@ run_ccs_injection(){ return $ret } -sub_session_ticket_tls() { +# Generic function to retrieve the session ticket via openssl. It is called via run_ticketbleed() only atm. +# Return value: string with TLS session ticket +# For ticketbleed SNI would not be needed as we assume ticketbleed is a vulnerability of the TLS stack. +# However for a generic function we would need SNI +#FIXME: We should consideer to do this earlier, and save it in a global variable. It would save 1x connect. +# +session_ticket_from_openssl() { local sessticket_tls="" - #FIXME: we likely have done this already before (either @ run_server_defaults() or at least the output - # from a previous handshake) --> would save 1x connect. We have TLS_TICKET but not yet the ticket itself #FIXME - #ATTENTION: we DO NOT use SNI here as we assume ticketbleed is a vulnerability of the TLS stack. If we'd do SNI here, we'd also need - # it in the ClientHello of run_ticketbleed() otherwise the ticket will be different and the whole thing won't work! - # - sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $OPTIMAL_PROTO $PROXY -connect $NODEIP:$PORT") $ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')" - sessticket_tls="$(sed -e 's/^.* - /x/g' -e 's/ .*$//g' <<< "$sessticket_tls" | tr '\n' ',')" - sed -e 's/ /,x/g' -e 's/-/,x/g' <<< "$sessticket_tls" + local line="" + local first=true + sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $OPTIMAL_PROTO $PROXY $SNI -connect $NODEIP:$PORT") $ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')" + + # This needs to be on stderr + debugme echo "$sessticket_tls" >&2 + # Now we extract the session ticket. First we'll remove the ASCII garbage (len=16chars) + # at the rhs, then we'll sqush all white spaces (normally it's just 3x " " but we're + # tolerant here. Then we remove evryth. up to the address, replace the dash in the middle. + # In the end we want commas between all bytes. + # Note the second expression requires "shopt -s extglob". + while read -r line; do + line="${line:0:$((${#line}-16))}" + line="${line%%+([[:space:]])}" + line="${line#*- }" + line="${line//-/ }" + line="${line// /,}" + if "$first"; then + # No comma in the beginning + printf "%s" "${line}" + first=false + else + printf "%s" ",${line}" + fi + done <<< "$sessticket_tls" } @@ -15276,7 +15302,7 @@ run_ticketbleed() { fi debugme echo "using protocol $tls_hexcode" - session_tckt_tls="$(sub_session_ticket_tls)" + session_tckt_tls="$(session_ticket_from_openssl)" if [[ "$session_tckt_tls" == , ]]; then pr_svrty_best "not vulnerable (OK)" outln ", no session tickets" @@ -15286,7 +15312,7 @@ run_ticketbleed() { fi len_tckt_tls=${#session_tckt_tls} - len_tckt_tls=$(( len_tckt_tls / 4)) + len_tckt_tls=$(( len_tckt_tls / 3)) # Attention: this also counts commas. So we have two bytes like 32,23, xlen_tckt_tls="$(dec02hex $len_tckt_tls)" len_handshake_record_layer="$(( len_sid + len_ch + len_tckt_tls ))" xlen_handshake_record_layer="$(dec04hex "$len_handshake_record_layer")" @@ -15374,9 +15400,9 @@ run_ticketbleed() { # Extension: SessionTicket TLS 00, 23, # length of SessionTicket TLS - 00, $xlen_tckt_tls, + 00, $xlen_tckt_tls , # data, Session Ticket - $session_tckt_tls # we have the comma already + $session_tckt_tls , # Extension: Heartbeat 00, 0f, 00, 01, 01" From dc6f9d7129c696225e30daf63eec1e55a1c12ae4 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 23 Mar 2020 11:26:17 +0100 Subject: [PATCH 6/7] Cleanup for ticketbleed (and other who need x509 client auth) run_ticketbleed() has now a check whether there's "$CLIENT_AUTH" set. If so a warn message is being issued and the test skipped. Empty replies for other reasons from the s_client connect are handled better within run_ticketbleed(). Otherwise it would lead to ugly errors on the console. Warning messages for vulneribility checks when client x509-based authentication is encountered are now all the same. CVE/CWE added. (run_renego(), run_breach() ). --- testssl.sh | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/testssl.sh b/testssl.sh index e978f3e31..484bcd9f1 100755 --- a/testssl.sh +++ b/testssl.sh @@ -15219,15 +15219,20 @@ session_ticket_from_openssl() { local line="" local first=true - sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $OPTIMAL_PROTO $PROXY $SNI -connect $NODEIP:$PORT") $ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')" - - # This needs to be on stderr + sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $OPTIMAL_PROTO $PROXY $SNI -connect $NODEIP:$PORT") $ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')" + # This needs to be on stderr (return value) debugme echo "$sessticket_tls" >&2 + if [[ -z "$sessticket_tls" ]] || [[ "$sessticket_tls" == " " ]]; then + echo "" + return 0 + fi + # Now we extract the session ticket. First we'll remove the ASCII garbage (len=16chars) # at the rhs, then we'll sqush all white spaces (normally it's just 3x " " but we're # tolerant here. Then we remove evryth. up to the address, replace the dash in the middle. # In the end we want commas between all bytes. # Note the second expression requires "shopt -s extglob". + while read -r line; do line="${line:0:$((${#line}-16))}" line="${line%%+([[:space:]])}" @@ -15302,13 +15307,20 @@ run_ticketbleed() { fi debugme echo "using protocol $tls_hexcode" - session_tckt_tls="$(session_ticket_from_openssl)" - if [[ "$session_tckt_tls" == , ]]; then - pr_svrty_best "not vulnerable (OK)" - outln ", no session tickets" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - debugme echo " session ticket TLS \"$session_tckt_tls\"" + if "$CLIENT_AUTH"; then + prln_warning "client x509-based authentication prevents this from being tested" + fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" "$cve" "$cwe" + # not sure yet whether this test w client auth would make sense at all return 0 + else + session_tckt_tls="$(session_ticket_from_openssl)" + if [[ "$session_tckt_tls" == , ]] || [[ -z "$session_tckt_tls" ]] ; then + pr_svrty_best "not vulnerable (OK)" + outln ", no session tickets" + fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" + debugme echo " session ticket TLS \"$session_tckt_tls\"" + return 0 + fi fi len_tckt_tls=${#session_tckt_tls} @@ -15598,7 +15610,7 @@ run_renego() { fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe" elif "$CLIENT_AUTH"; then prln_warning "client x509-based authentication prevents this from being tested" - fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" + fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" "$cve" "$cwe" sec_client_renego=1 else # We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background @@ -15789,8 +15801,9 @@ run_breach() { [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln pr_bold " BREACH"; out " ($cve) " if "$CLIENT_AUTH"; then - outln "cannot be tested (server side requires x509 authentication)" - fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe" + prln_warning "client x509-based authentication prevents this from being tested" + fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" "$cve" "$cwe" + return 0 fi # if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then From fab5da559577902a740c2e75d0c31deff2e0416e Mon Sep 17 00:00:00 2001 From: Dirk Date: Thu, 26 Nov 2020 12:19:51 +0100 Subject: [PATCH 7/7] Fix at least one error in travis/CI --- testssl.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index 56d9dd815..6b4823a1d 100755 --- a/testssl.sh +++ b/testssl.sh @@ -15761,14 +15761,17 @@ run_ccs_injection(){ } sub_session_ticket_tls() { - local tls_proto="$1" local sessticket_tls="" + local line="" + local tls_proto="$1" + local first=true + #FIXME: we likely have done this already before (either @ run_server_defaults() or at least the output # from a previous handshake) --> would save 1x connect. We have TLS_TICKET but not yet the ticket itself # We DO NOT use SNI here as we assume ticketbleed is a TLS stack. vulnerability. If we'd use SNI here, we'd also need # it to use in the ClientHello of run_ticketbleed() otherwise the ticket will be different and the whole thing won't work! # - sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $tls_proto $PROXY -connect $NODEIP:$PORT") $ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')" + sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $tls_proto $PROXY $SNI -connect $NODEIP:$PORT") $ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')" debugme echo "$sessticket_tls" >&2 # This needs to be on stderr (return value) if [[ -z "$sessticket_tls" ]] || [[ "$sessticket_tls" == " " ]]; then