diff --git a/testssl.sh b/testssl.sh index 6c61516..13c633d 100755 --- a/testssl.sh +++ b/testssl.sh @@ -15884,15 +15884,48 @@ run_crime() { } -# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic -# to the version of TLS/SSL, more: http://www.breachattack.com/ . Foreign referrers are the important thing here! + +# As the name says. It expects as arg1 a GET command string. It returns 1 +# when GET command was stalled or killed (which is no not always used) +# and echos "warn_*". It return 0 when everything went ok and echos the +# compression if any. +sub_breach_helper() { + local get_command="$1" + local detected_compression="" + local -i was_killed=0 + + safe_echo "$get_command" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE & + wait_kill $! $HEADER_MAXSLEEP + was_killed=$? # !=0 when it was killed + detected_compression=$(grep -ia ^Content-Encoding: $TMPFILE) + detected_compression="$(strip_lf "$detected_compression")" + detected_compression="${detected_compression#*:}" + detected_compression="$(strip_spaces "$detected_compression")" + if [[ ! -s $TMPFILE ]]; then + if [[ $was_killed -eq 0 ]]; then + echo "warn_stalled" + else + echo "warn_killed" + fi + return 1 + elif [[ -z $detected_compression ]]; then + echo "no_compression" + else + echo "$detected_compression" + fi + return 0 +} + + + +# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic to the +# version of TLS/SSL, more: http://www.breachattack.com/ . External referrers are the important thing here! # Mitigation: see https://community.qualys.com/message/20360 # Any URL can be vulnerable. Here only the given URL is tested. See also $when_makesense # run_breach() { local header local -i ret=0 - local -i was_killed=0 local referer useragent local url="$1" local spaces=" " @@ -15904,6 +15937,8 @@ run_breach() { local jsonID="BREACH" local compressions="gzip deflate compress br" local has_compression=() + local detected_compression="" + local get_command="" [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7 @@ -15914,13 +15949,6 @@ run_breach() { fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe" fi - # if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then - # pr_warning "Retrieving HTTP header failed before. Skipping." - # fileout "$jsonID" "WARN" "HTTP response was wampty before" "$cve" "$cwe" - # outln - # return 1 - # fi - [[ -z "$url" ]] && url="/" disclaimer=" - only supplied \"$url\" tested" @@ -15928,55 +15956,80 @@ run_breach() { [[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com useragent="$UA_STD" $SNEAKY && useragent="$UA_SNEAKY" - for c in $compressions; do - tm_out "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: $c\r\nAccept: text/*\r\n\r\n" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE & - wait_kill $! $HEADER_MAXSLEEP - was_killed=$? # !=0 was killed - result=$(grep -ia ^Content-Encoding: $TMPFILE) - debugme echo "$result" - result="$(strip_lf "$result")" - result="${result#*:}" - result="$(strip_spaces "$result")" - if [[ ! -s $TMPFILE ]]; then - if [[ $was_killed -ne 0 ]]; then - has_compression+=("$c:warn_stalled") - else - has_compression+=("$c:warn_empty") - fi - ret+=1 - elif [[ -z $result ]]; then - has_compression+=("$c:no") - else - has_compression+=("$c:yes") - fi - done - result="" - if [[ ! ${has_compression[@]} =~ yes ]] && [[ ! ${has_compression[@]} =~ warn ]]; then - pr_svrty_best "no gzip/deflate/compress/br HTTP compression (OK) " - outln "$disclaimer" - fileout "$jsonID" "OK" "not vulnerable, no gzip/deflate/compress/br HTTP compression $disclaimer" "$cve" "$cwe" - elif [[ ${has_compression[@]} =~ warn_stalled ]]; then - pr_warning "At least 1/4 checks failed (HTTP header request stalled and was terminated" - out ", debug: ${has_compression[@]})" - fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe" - elif [[ ${has_compression[@]} =~ warn_empty ]]; then - pr_warning "At least 1/4 checks failed (HTTP header request was empty, debug: ${has_compression[@]}" - out ", debug: ${has_compression[@]})" - fileout "$jsonID" "WARN" "Test failed as HTTP response was empty, debug: ${has_compression[@]}" "$cve" "$cwe" - else - for c in ${has_compression[@]}; do - if [[ $c =~ yes ]]; then - result+="${c%:*} " + # Assemble the GET command with all available compressions and send them all, initially. + # If the result is negative: we can just tell the finding and return. If it's + # positive: We already have identified 1x compression + get_command="GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: ${compressions// /,}\r\nAccept: text/*\r\n\r\n" + detected_compression=$(sub_breach_helper "$get_command") + case "$detected_compression" in + warn_stalled) + pr_warning "First request failed (HTTP header request stalled and was terminated)" + fileout "$jsonID" "WARN" "Test failed as first HTTP request stalled and was terminated" "$cve" "$cwe" + ret=1 + ;; + warn_failed) + pr_warning "First request failed (HTTP header request was empty)" + fileout "$jsonID" "WARN" "Test failed as first HTTP response was empty" "$cve" "$cwe" + ret=1 + ;; + no_compression) + pr_svrty_best "no gzip/deflate/compress/br HTTP compression (OK) " + outln "$disclaimer" + fileout "$jsonID" "OK" "not vulnerable, no gzip/deflate/compress/br HTTP compression $disclaimer" "$cve" "$cwe" + ret=0 + ;; + *) # Now assemble the remaining compressions in $compressions and loop through them + has_compression+=("$detected_compression:yes") + compressions="${compressions//$detected_compression/}" + for c in $compressions; do + get_command="GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: ${c}\r\nAccept: text/*\r\n\r\n" + detected_compression=$(sub_breach_helper "$get_command") + if [[ $? -ne 0 ]]; then + # This failure unlikely here. The initial request must have succeeded and this one then + # failed but we'd rather treat this correctly (e.d. IDS which triggers later). Not also + # we exit on the first stalled request. So if the first one with all compressions failed, + # we don't get here. It seems very unlikely the first failed and subsequent will succeed. + has_compression+=("$c:$compressions") + elif [[ "$detected_compression" =~ no_compression ]]; then + has_compression+=("$c:no") + debugme echo "has_compression: $c: no" + elif [[ -n "detected_compression" ]]; then + has_compression+=("$c:yes") + debugme echo "has_compression: $c: yes" + else + prln_fixme "strange reply around line $((LINENO)) from sub_breach_helper()" + fi + done + + # Final verdict (if not happened preemptively before). We reuse $detected_compression here + detected_compression="" + if [[ ${has_compression[@]} =~ warn ]]; then + # warn_empty / warn_stalled + if [[ ${has_compression[@]} =~ warn_empty ]]; then + pr_warning "At least 1/4 checks failed (HTTP header request was empty, debug: ${has_compression[@]}" + out ", debug: ${has_compression[@]})" + fileout "$jsonID" "WARN" "Test failed as HTTP response was empty, debug: ${has_compression[@]}" "$cve" "$cwe" + else # warn_stalled + pr_warning "At least 1/4 checks failed (HTTP header request stalled and was terminated" + out ", debug: ${has_compression[@]})" + fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe" + fi + else + for c in ${has_compression[@]}; do + if [[ $c =~ yes ]]; then + detected_compression+="${c%:*} " + fi + done + detected_compression="$(strip_trailing_space "$detected_compression")" + pr_svrty_high "potentially NOT ok, \"$detected_compression\" HTTP compression detected." + outln "$disclaimer" + outln "${spaces}${when_makesense}" + fileout "$jsonID" "HIGH" "potentially VULNERABLE, $detected_compression HTTP compression detected $disclaimer" "$cve" "$cwe" "$hint" fi - done - result="$(strip_trailing_space "$result")" - pr_svrty_high "potentially NOT ok, \"$result\" HTTP compression detected." - outln "$disclaimer" - outln "$spaces$when_makesense" - fileout "$jsonID" "HIGH" "potentially VULNERABLE, $result HTTP compression detected $disclaimer" "$cve" "$cwe" "$hint" - fi - debugme echo "has_compression: ${has_compression[@]}" + debugme outln "${spaces}has_compression: ${has_compression[@]}" + ;; + esac tmpfile_handle ${FUNCNAME[0]}.txt return $ret