From 825cbf7b1deafe89f6a05e80bad5b2d88db3bf0a Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 11 May 2020 10:41:48 +0200 Subject: [PATCH] Improve compression detection for BREACH This commit tries to enummerate through all possible compressions instead of just raising the arm because of the first one detected. As far as the performance is concerned there's room for improvements which subsequent commits will address. --- testssl.sh | 67 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/testssl.sh b/testssl.sh index 39c67e3..6c61516 100755 --- a/testssl.sh +++ b/testssl.sh @@ -15887,6 +15887,7 @@ 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! # 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 @@ -15899,8 +15900,10 @@ run_breach() { local when_makesense=" Can be ignored for static pages or if no secrets in the page" local cve="CVE-2013-3587" local cwe="CWE-310" - local hint="" + local hint="" c="" local jsonID="BREACH" + local compressions="gzip deflate compress br" + local has_compression=() [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7 @@ -15925,33 +15928,55 @@ run_breach() { [[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com useragent="$UA_STD" $SNEAKY && useragent="$UA_SNEAKY" - tm_out "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: gzip,deflate,compress\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=$(awk '/^Content-Encoding/ { print $2 }' $TMPFILE) - result=$(strip_lf "$result") - debugme grep '^Content-Encoding' $TMPFILE - if [[ ! -s $TMPFILE ]]; then - pr_warning "failed (HTTP header request stalled or empty return" - if [[ $was_killed -ne 0 ]]; then - pr_warning " and was terminated" - fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe" + 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 - fileout "$jsonID" "WARN" "Test failed as HTTP response was empty" "$cve" "$cwe" + has_compression+=("$c:yes") fi - prln_warning ") " - ret=1 - elif [[ -z $result ]]; then - pr_svrty_best "no HTTP compression (OK) " + 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 HTTP compression $disclaimer" "$cve" "$cwe" + 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 - pr_svrty_high "potentially NOT ok, uses $result HTTP compression." + for c in ${has_compression[@]}; do + if [[ $c =~ yes ]]; then + result+="${c%:*} " + 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, uses $result HTTP compression $disclaimer" "$cve" "$cwe" "$hint" + fileout "$jsonID" "HIGH" "potentially VULNERABLE, $result HTTP compression detected $disclaimer" "$cve" "$cwe" "$hint" fi - # Any URL can be vulnerable. I am testing now only the given URL! + debugme echo "has_compression: ${has_compression[@]}" tmpfile_handle ${FUNCNAME[0]}.txt return $ret