From 241b6e4d2e992a30595a6cee7eeaf327921ad522 Mon Sep 17 00:00:00 2001 From: Dirk Date: Mon, 12 Jun 2017 18:23:55 +0200 Subject: [PATCH] parallel mass testing mode, Ticketbleed+client auth, parallel mode also for nmap Parallel mass testing mode is now not anymore experimental. To use it a separate flag ``--mode=parallel`` was introduced. Serial is still the default for now to avoid unexpected conditions. Both the mode arguement and the default is subject to change. The parallel mass testing mode can now also make use of a nmap file. Also the functional test for nmap file was put into a separate function and made more user safe. Open point is that we better should use the hostname if the forward DNS record matches. Fixed logical inconsistency: Ticketbleed was not being tested against a server with client authentication Some variables in the beginning reordered --- testssl.sh | 92 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/testssl.sh b/testssl.sh index 2af36eb13..75202376c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -161,6 +161,7 @@ if [[ -z $TERM_WIDTH ]]; then # no batch file and fi TERM_CURRPOS=0 # custom line wrapping needs alter the current horizontal cursor pos +## CONFIGURATION PART ## # following variables make use of $ENV, e.g. OPENSSL= ./testssl.sh # 0 means (normally) true here. Some of the variables are also accessible with a command line switch, see --help @@ -183,6 +184,7 @@ DEBUG=${DEBUG:-0} # 1: normal putput the files in /tmp/ ar # 6: whole 9 yards FAST=${FAST:-false} # preference: show only first cipher, run_allciphers with openssl instead of sockets WIDE=${WIDE:-false} # whether to display for some options just ciphers or a table w hexcode/KX,Enc,strength etc. +MASS_TESTING_MODE=${MASS_TESTING_MODE:-serial} # can be serial or parallel. Subject to change LOGFILE="${LOGFILE:-""}" # logfile if used JSONFILE="${JSONFILE:-""}" # jsonfile if used CSVFILE="${CSVFILE:-""}" # csvfile if used @@ -228,7 +230,13 @@ if [[ -n "$MEASURE_TIME_FILE" ]] && [[ -z "$MEASURE_TIME" ]]; then else MEASURE_TIME=${MEASURE_TIME:-false} fi +DISPLAY_CIPHERNAMES="openssl" # display OpenSSL ciphername (but both OpenSSL and RFC ciphernames in wide mode) +SHOW_CENSYS_LINK=${SHOW_CENSYS_LINK:-true} +readonly UA_STD="TLS tester from $SWURL" +readonly UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" + +## INITIALIZATION PART ## # further global vars just declared here readonly NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1" # alpn_protos needs to be space-separated, not comma-seperated, including odd ones observerd @ facebook and others, old ones like h2-17 omitted as they could not be found @@ -267,7 +275,6 @@ HAS_FALLBACK_SCSV=false HAS_PROXY=false HAS_XMPP=false HAS_POSTGRES=false -DISPLAY_CIPHERNAMES="openssl" # display OpenSSL ciphername (but both OpenSSL and RFC ciphernames in wide mode) PORT=443 # unless otherwise auto-determined, see below NODE="" NODEIP="" @@ -285,7 +292,6 @@ SERVICE="" # is the server running an HTTP server, URI="" CERT_FINGERPRINT_SHA2="" RSA_CERT_FINGERPRINT_SHA2="" -SHOW_CENSYS_LINK=${SHOW_CENSYS_LINK:-true} STARTTLS_PROTOCOL="" OPTIMAL_PROTO="" # we need this for IIS6 (sigh) and OpenSSL 1.0.2, otherwise some handshakes # will fail, see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 @@ -295,8 +301,6 @@ TLS_NOW="" NOW_TIME="" HTTP_TIME="" GET_REQ11="" -readonly UA_STD="TLS tester from $SWURL" -readonly UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" START_TIME=0 # time in epoch when the action started END_TIME=0 # .. ended SCAN_TIME=0 # diff of both: total scan time @@ -1279,7 +1283,7 @@ service_detection() { local -i was_killed local addcmd="" - if ! $CLIENT_AUTH; then + if ! "$CLIENT_AUTH"; then # SNI is not standardardized for !HTTPS but fortunately for other protocols s_client doesn't seem to care [[ ! "$1" =~ ssl ]] && addcmd="$SNI" printf "$GET_REQ11" | $OPENSSL s_client $1 -quiet $BUGS -connect $NODEIP:$PORT $PROXY $addcmd >$TMPFILE 2>$ERRFILE & @@ -1305,7 +1309,7 @@ service_detection() { fileout "service" "INFO" "Service detected: $SERVICE, thus skipping HTTP specific checks" ret=0 ;; - *) if $CLIENT_AUTH; then + *) if "$CLIENT_AUTH"; then out "certificate based authentication => skipping all HTTP checks" echo "certificate based authentication => skipping all HTTP checks" >$TMPFILE fileout "client_auth" "INFO" "certificate based authentication => skipping all HTTP checks" @@ -9178,7 +9182,7 @@ run_ticketbleed() { [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Ticketbleed vulnerability " && outln pr_bold " Ticketbleed"; out " ($cve), experiment. " - if [[ "$SERVICE" != HTTP ]]; then + if [[ "$SERVICE" != HTTP ]] && ! "$CLIENT_AUTH"; then outln "-- (applicable only for HTTPS)" fileout "ticketbleed" "INFO" "Ticketbleed: not applicable, not HTTP" "$cve" "$cwe" return 0 @@ -9562,7 +9566,7 @@ run_crime() { ret=7 elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then pr_done_good "not vulnerable (OK)" - if [[ $SERVICE != "HTTP" ]] && ! $CLIENT_AUTH; then + if [[ $SERVICE != "HTTP" ]] && ! "$CLIENT_AUTH"; then out " (not using HTTP anyway)" fileout "crime" "OK" "CRIME, TLS: Not vulnerable (not using HTTP anyway)" "$cve" "$cwe" else @@ -9635,10 +9639,14 @@ run_breach() { local cwe="CWE-310" local hint="" - [[ $SERVICE != "HTTP" ]] && return 7 + [[ $SERVICE != "HTTP" ]] && ! "$CLIENT_AUTH" && return 7 [[ $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 authentication" + fileout "breach" "INFO" "BREACH: Test failed (HTTP request stalled)" "$cve" "$cwe" + fi url="$1" [[ -z "$url" ]] && url="/" @@ -11124,13 +11132,14 @@ help() { "$PROG_NAME URI", where is: - -t, --starttls does a default run against a STARTTLS enabled Does a default run against a STARTTLS enabled (latter three require supplied openssl) - --xmpphost for STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed - --mx tests MX records from high to low priority (STARTTLS, port 25) - --file mass testing option: Reads command lines from , one line per instance. + --xmpphost For STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed + --mx Tests MX records from high to low priority (STARTTLS, port 25) + --file Mass testing option: Reads command lines from , one line per instance. Comments via # allowed, EOF signals end of . Implicitly turns on "--warnings batch". Alternatively: nmap output in greppable format (-oG) is also allowed (1x same port per line) + --mode Mass testing to be done serial (default) or parallel single check as ("$PROG_NAME URI" does everything except -E): -e, --each-cipher checks each local cipher remotely @@ -12324,6 +12333,18 @@ nmap_to_plain_file() { local target_fname="" local oneline="" + # Ok, since we are here we have an nmap file. To avoid questions we make sure it's the right format too + if [[ "$(head -1 "$FNAME")" =~ ( -oG )(.*) ]]; then + # yes, grappable + if [[ $(grep -c Status "$FNAME") -ge 1 ]]; then + [[ $(grep -c '\/open\/' $FNAME) -eq 0 ]] && \ + fatal "Nmap file $FNAME should contain at least one open port" -1 + else + fatal "strange, nmap grepable misses \"Status\"" -1 + fi + else + fatal "Nmap file $FNAME is not in grep(p)able format (-oG filename.gmap)" -1 + fi # test whether there's more than one "open" per line which is not supported currently while read -r oneline; do if [[ $(tr ',' '\n' <<< "$oneline" | grep -c '\/open\/') -gt 1 ]]; then @@ -12331,6 +12352,10 @@ nmap_to_plain_file() { fi done < "$FNAME" target_fname=${FNAME%.*}.txt # strip extension +#FIXME: # nmap in mass testing mode supplies the hostname too in $3. That's normally unreliable +# as a DNS reverse lookup used and reality shows those records are not as good maintained. +# However a check whether the forward lookup matches the IP should be done and if so we should +# should rather use the fqdn And: better use read here awk '/\/open\// { print $2":"$5 }' "$FNAME" | sed 's/\/open.*$//g' >"$target_fname" [[ $? -ne 0 ]] && \ fatal "conversion from nmap grepable to text somehow failed around $LINENO" -3 @@ -12343,29 +12368,18 @@ run_mass_testing() { local cmdline="" local first=true local gmapadd="" + local saved_fname="$FNAME" if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then fatal "Can't read file \"$FNAME\"" "2" fi - # at least now we checked the command line. But it's not sure yet whether we have the right file + if [[ "$(head -1 "$FNAME")" =~ (Nmap [4-8])(.*)( scan initiated )(.*) ]]; then - # Ok, we have an nmap file. To avoid questions we make sure it's the right format too - if [[ "$(head -1 "$FNAME")" =~ ( -oG )(.*) ]]; then - if [[ $(grep -c Status "$FNAME") -ge 1 ]]; then - [[ $(grep -c '\/open\/' $FNAME) -eq 0 ]] && \ - fatal "Nmap file $FNAME should contain at least one open port" -1 - IS_GMAP_FILE=true - gmapadd="grep(p)able nmap " - nmap_to_plain_file - else - fatal "wierdly nmap grepable misses \"Status\"" -1 - fi - else - fatal "Nmap file $FNAME is not in grep(p)able format (-oG filename.gmap)" -1 - fi + gmapadd="grep(p)able nmap " + nmap_to_plain_file fi - pr_reverse "====== Running in file batch mode with ${gmapadd}file=\"$FNAME\" ======"; outln "\n" + pr_reverse "====== Running in file batch mode with ${gmapadd}file=\"$saved_fname\" ======"; outln "\n" while read cmdline; do cmdline="$(filter_input "$cmdline")" [[ -z "$cmdline" ]] && continue @@ -12424,12 +12438,19 @@ run_mass_testing_parallel() { local -i i nr_active_tests=0 local -a -i start_time=() local -i curr_time wait_time + local gmapadd="" + local saved_fname="$FNAME" if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then fatal "Can't read file \"$FNAME\"" "2" fi - pr_reverse "====== Running in parallel file batch mode with file=\"$FNAME\" ======"; outln "\n" + if [[ "$(head -1 "$FNAME")" =~ (Nmap [4-8])(.*)( scan initiated )(.*) ]]; then + gmapadd="grep(p)able nmap " + nmap_to_plain_file + fi + + pr_reverse "====== Running in file batch mode with ${gmapadd}file=\"$saved_fname\" ======"; outln "\n" while read cmdline; do cmdline="$(filter_input "$cmdline")" [[ -z "$cmdline" ]] && continue @@ -12915,6 +12936,15 @@ parse_cmd_line() { WARNINGS=batch # set this implicitly! do_mass_testing=true ;; + --mode|--mode=*) + MASS_TESTING_MODE="$(parse_opt_equal_sign "$1" "$2")" + [[ $? -eq 0 ]] && shift + case "$MASS_TESTING_MODE" in + serial|parallel) ;; + *) tmln_magenta "\nmass testing mode can be either \"serial\" or \"parallel\"" + help 1 + esac + ;; --warnings|--warnings=*) WARNINGS=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift @@ -13251,7 +13281,7 @@ lets_roll() { if "$do_mass_testing"; then prepare_logging - if "$EXPERIMENTAL"; then + if [[ "$MASS_TESTING_MODE" == "parallel" ]]; then run_mass_testing_parallel else run_mass_testing