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); + + diff --git a/testssl.sh b/testssl.sh index ef1494b2d..6b4823a1d 100755 --- a/testssl.sh +++ b/testssl.sh @@ -69,6 +69,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 # @@ -766,8 +769,9 @@ debugme1() { return 0 } -hex2dec() { - echo $((16#$1)) +debugme1() { + [[ "$DEBUG" -ge 1 ]] && "$@" + return 0 } # convert 414243 into ABC @@ -779,15 +783,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}" } @@ -10747,7 +10756,7 @@ starttls_nntp_dialog() { starttls_postgres_dialog() { local -i ret=0 local debugpad=" > " - local starttls_init=", x00, x00 ,x00 ,x08 ,x04 ,xD2 ,x16 ,x2F" + local starttls_init=", 00, 00 ,00 ,08 ,04 ,D2 ,16 ,2F" debugme echo "=== starting postgres STARTTLS dialog ===" socksend "${starttls_init}" 0 && debugme echo "${debugpad}initiated STARTTLS" && @@ -10761,17 +10770,18 @@ starttls_mysql_dialog() { local debugpad=" > " local -i ret=0 local starttls_init=" - , 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" + , 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" debugme echo "=== starting mysql STARTTLS dialog ===" socksend "${starttls_init}" 0 && debugme echo "${debugpad}initiated STARTTLS" && starttls_just_read 1 "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 # but atm this seems sufficient as later we will fail if there's no STARTTLS. @@ -10912,19 +10922,57 @@ 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 ",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 } # 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")" +} + +# 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 (${line/_/}) called from $2 is not ok" + elif [[ ${line:0:1} != _ ]]; then + echo "char $i (${line/_/}) called from $2 doesn't start with a \"\\\"" + elif [[ ${line:1:1} != x ]]; then + 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 (${line/_/}) called from $2 is not hex" + fi + i+=1 + done < <( echo -e ${1//\\/\\n_}) } # sockets inspired by https://blog.chris007.de/using-bash-for-network-socket-operation/ @@ -10935,7 +10983,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 & @@ -10948,10 +10999,10 @@ 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 - # read line per line and strip comments (bash internal func can't handle multiline statements data="$(while read line; do printf "${line%%\#*}" done <<< "$1" )" @@ -15055,7 +15106,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")" @@ -15418,7 +15469,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="" @@ -15445,29 +15496,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 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! + # 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="$(sed -e 's/^.* - /x/g' -e 's/ .*$//g' <<< "$sessticket_tls" | tr '\n' ',')" - sed -e 's/ /,x/g' -e 's/-/,x/g' <<< "$sessticket_tls" + 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 + 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:]])}" + 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" } @@ -15729,8 +15806,8 @@ run_ticketbleed() { local tls_hexcode tls_proto="" 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="" @@ -15756,7 +15833,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" @@ -15764,21 +15841,21 @@ run_ticketbleed() { fi if [[ 0 -eq $(has_server_protocol tls1) ]]; then - tls_hexcode="x03, x01"; tls_proto="-tls1" + tls_hexcode="03,01" elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then - tls_hexcode="x03, x02"; tls_proto="-tls1_1" + tls_hexcode="03,02" elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then - tls_hexcode="x03, x03"; tls_proto="-tls1_2" + tls_hexcode="03,03" elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then - tls_hexcode="x03, x00"; tls_proto="-ssl3" + tls_hexcode="03,00" else # no protocol for some reason defined, determine TLS versions offered with a new handshake "$HAS_TLS13" && tls_proto="-no_tls1_3" $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS $tls_proto -connect $NODEIP:$PORT $PROXY") >$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))"