From 5de873f8bc71419ae206457ac054fcf025773af4 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Tue, 12 Dec 2017 09:31:06 -0500 Subject: [PATCH] Test for vulnerability to Bleichenbacher attack This PR adds a test to check whether a server that supports ciphers suites that use RSA key transport (TLS_RSA) are vulnerable to Bleichenbacher attacks (see http://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf). --- CREDITS.md | 1 + Readme.md | 1 + doc/testssl.1 | 3 + doc/testssl.1.md | 2 + testssl.sh | 258 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 263 insertions(+), 2 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index dbda2c8..2be09fc 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -15,6 +15,7 @@ - numerous fixes - better error msg suppression (not fully installed openssl - GREASE support + - Bleichenbacher vulnerability test - TLS 1.3 support ##### Credits also to diff --git a/Readme.md b/Readme.md index f302f84..912520b 100644 --- a/Readme.md +++ b/Readme.md @@ -74,6 +74,7 @@ Update notification here or @ [twitter](https://twitter.com/drwetter). * Better formatting of output (indentation) * Choice showing the RFC naming scheme only * LUCKY13 and SWEET32 checks +* Check for vulnerability to Bleichenbacher attacks * Ticketbleed check * Decoding of unencrypted BIG IP cookies * LOGJAM: now checking also for known DH parameters diff --git a/doc/testssl.1 b/doc/testssl.1 index 69ae475..c0b0df3 100644 --- a/doc/testssl.1 +++ b/doc/testssl.1 @@ -260,6 +260,9 @@ Security headers (X\-Frame\-Options, X\-XSS\-Protection, \.\.\., CSP headers) \fB\-T, \-\-ticketbleed\fR Checks for Ticketbleed memory leakage in BigIP loadbalancers\. . .P +\fB\-BB, \-\-robot\fR Checks for vulnerability to Bleichenbacher attacks\. +. +.P \fB\-R, \-\-renegotiation\fR Tests renegotiation vulnerabilities\. Currently there\'s a check for "Secure Renegotiation" and for "Secure Client\-Initiated Renegotiation"\. Please be aware that vulnerable servers to the latter can likely be DoSed very easily (HTTP)\. A check for "Insecure Client\-Initiated Renegotiation" is not yet implemented\. . .P diff --git a/doc/testssl.1.md b/doc/testssl.1.md index 4e255c9..6c5126f 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -176,6 +176,8 @@ If the server provides no matching record in Subject Alternative Name (SAN) but `-T, --ticketbleed` Checks for Ticketbleed memory leakage in BigIP loadbalancers. +`-BB, --robot` Checks for vulnerability to Bleichenbacher attacks. + `-R, --renegotiation` Tests renegotiation vulnerabilities. Currently there's a check for "Secure Renegotiation" and for "Secure Client-Initiated Renegotiation". Please be aware that vulnerable servers to the latter can likely be DoSed very easily (HTTP). A check for "Insecure Client-Initiated Renegotiation" is not yet implemented. `-C, --compression, --crime` Checks for CRIME ("Compression Ratio Info-leak Made Easy") vulnerability in TLS. CRIME in SPDY is not yet being checked for. diff --git a/testssl.sh b/testssl.sh index 89fc23f..49c369c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -12876,6 +12876,252 @@ run_grease() { fi } +# If the server supports any non-PSK cipher suites that use RSA key transport, +# check if the server is vulnerable to Bleichenbacher's Oracle Threat (ROBOT) attacks. +# See "Return Of Bleichenbacher's Oracle Threat Or how we signed a Message with +# Facebook's Private Key" by Hanno Böck, Juraj Somorovsky, and Craig Young. +run_robot() { + local tls_hexcode="03" + # A list of all non-PSK cipher suites that use RSA key transport + local cipherlist="00,9d, c0,a1, c0,9d, 00,3d, 00,35, 00,c0, 00,84, c0,3d, c0,51, c0,7b, ff,00, ff,01, ff,02, ff,03, c0,a0, c0,9c, 00,9c, 00,3c, 00,2f, 00,ba, 00,96, 00,41, 00,07, c0,3c, c0,50, c0,7a, 00,05, 00,04, 00,0a, fe,ff, ff,e0, 00,62, 00,09, 00,61, fe,fe, ff,e1, 00,64, 00,60, 00,08, 00,06, 00,03, 00,3b, 00,02, 00,01" + # A list of all non-PSK cipher suites that use RSA key transport and that use AES in either GCM or CBC mode. + local aes_gcm_cbc_cipherlist="00,9d, 00,9c, 00,3d, 00,35, 00,3c, 00,2f" + local padded_pms encrypted_pms cke_prefix client_key_exchange rnd_pad + local rnd_pms="aa112233445566778899112233445566778899112233445566778899112233445566778899112233445566778899" + local change_cipher_spec finished resp + local -a response + local -i i ret len iteration testnum pubkeybits pubkeybytes + local vulnerable=false send_ccs_finished=true + local -i start_time end_time timeout=$MAX_WAITSOCK + local cve="" + local cwe="" + + [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability " && outln + pr_bold " ROBOT " + + if [[ 0 -eq $(has_server_protocol tls1_2) ]]; then + tls_hexcode="03" + elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then + tls_hexcode="02" + elif [[ 0 -eq $(has_server_protocol tls1) ]]; then + tls_hexcode="01" + elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then + tls_hexcode="00" + fi + + # Some hosts are only vulnerable with GCM. First send a list of + # ciphers that use AES in GCM or CBC mode, with the GCM ciphers + # listed first, and then try all ciphers that use RSA key transport + # if there is no connection on the first try. + tls_sockets "$tls_hexcode" "$aes_gcm_cbc_cipherlist" + ret=$? + if [[ $ret -eq 0 ]] || [[ $ret -eq 2 ]]; then + cipherlist="$aes_gcm_cbc_cipherlist" + tls_hexcode="${DETECTED_TLS_VERSION:2:2}" + else + if [[ "$tls_hexcode" != "03" ]]; then + cipherlist="$(strip_inconsistent_ciphers "$tls_hexcode" ", $cipherlist")" + cipherlist="${cipherlist:2}" + fi + tls_sockets "$tls_hexcode" "$cipherlist" + ret=$? + if [[ $ret -eq 2 ]]; then + tls_hexcode="${DETECTED_TLS_VERSION:2:2}" + cipherlist="$(strip_inconsistent_ciphers "$tls_hexcode" ", $cipherlist")" + cipherlist="${cipherlist:2}" + elif [[ $ret -ne 0 ]]; then + prln_done_best "Server does not support any cipher suites that use RSA key transport" + fileout "ROBOT" "OK" "ROBOT: not vulnerable (server does not support any cipher suites that use RSA key transport)" + return 0 + fi + fi + + # Run the tests in two iterations. In iteration 0, send 5 different client + # key exchange (CKE) messages followed by change cipher spec (CCS) and + # Finished messages, and check whether the server provided the same + # response in each case. If the server didn't provide the same response + # for all five messages in iteration 0, then it is vulnerable. Otherwise + # try a second time (iteration 1) with the same CKE messages, but without + # sending the CCS or Finished messages. + # Iterations 0 and 1 are run with a short timeout waiting for the server + # to respond to the CKE message. If the server was found to be potentially + # vulnerable in iteration 0 or 1 and testssl.sh timed out waiting for a + # response in some cases, then retry the test using a longer timeout value. + for (( iteration=0; iteration < 3; iteration++ )); do + if [[ $iteration -eq 1 ]]; then + # If the server was found to be vulnerable in iteration 0, then + # there's no need to try the alternative message flow. + "$vulnerable" && continue + send_ccs_finished=false + elif [[ $iteration -eq 2 ]]; then + # The tests are being rerun, so reset the vulnerable flag. + vulnerable=false + fi + for (( testnum=0; testnum < 5; testnum++ )); do + response[testnum]="untested" + done + for (( testnum=0; testnum < 5; testnum++ )); do + tls_sockets "$tls_hexcode" "$cipherlist" "all" "" "" "false" + + # Create the padded premaster secret to encrypt. The padding should be + # of the form "00 02 00 ." + # However, for each test except testnum=0 the padding will be + # made incorrect in some way, as specified below. + + # Determine the length of the public key and create the bytes. + # should be a length that makes total length of $padded_pms + # the same as the length of the public key. should contain no 00 bytes. + pubkeybits="$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | \ + $OPENSSL pkey -pubin -text 2>>$ERRFILE | grep -aw "Public-Key:" | \ + sed -e 's/.*(//' -e 's/ bit)//')" + pubkeybytes=$pubkeybits/8 + [[ $((pubkeybits%8)) -ne 0 ]] && pubkeybytes+=1 + rnd_pad="" + for (( len=0; len < pubkeybytes-52; len=len+2 )); do + rnd_pad+="abcd" + done + [[ $len -eq $pubkeybytes-52 ]] && rnd_pad+="ab" + + case "$testnum" in + # correct padding + 0) padded_pms="0002${rnd_pad}00${DETECTED_TLS_VERSION}${rnd_pms}" ;; + # wrong first two bytes + 1) padded_pms="4117${rnd_pad}00${DETECTED_TLS_VERSION}${rnd_pms}" ;; + # 0x00 on a wrong position + 2) padded_pms="0002${rnd_pad}11${rnd_pms}0011" ;; + # no 0x00 in the middle + 3) padded_pms="0002${rnd_pad}111111${rnd_pms}" ;; + # wrong version number (according to Klima / Pokorny / Rosa paper) + 4) padded_pms="0002${rnd_pad}000202${rnd_pms}" ;; + esac + + # Encrypt the padded premaster secret using the server's public key. + encrypted_pms="$(asciihex_to_binary_file "$padded_pms" "/dev/stdout" | \ + $OPENSSL pkeyutl -encrypt -certin -inkey $HOSTCERT -pkeyopt rsa_padding_mode:none 2>/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 + else + socksend ",x15, x03, x01, x00, x02, x02, x00" 0 + fi + close_socket + prln_local_problem "Your $OPENSSL does not support the pkeyutl utility." + fileout "ROBOT" "WARN" "Your $OPENSSL does not support the pkeyutl utility." + return 1 + fi + + # Create the client key exchange message. + len=${#encrypted_pms}/2 + cke_prefix="16${DETECTED_TLS_VERSION}$(printf "%04x" $((len+6)))10$(printf "%06x" $((len+2)))$(printf "%04x" $len)" + encrypted_pms="$cke_prefix$encrypted_pms" + len=${#encrypted_pms} + client_key_exchange="" + for (( i=0; i ("$PROG_NAME URI" does everything except -E and -g): -H, --heartbleed tests for Heartbleed vulnerability -I, --ccs, --ccs-injection tests for CCS injection vulnerability -T, --ticketbleed tests for Ticketbleed vulnerability in BigIP loadbalancers + -BB, --robot tests for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability -R, --renegotiation tests for renegotiation vulnerabilities -C, --compression, --crime tests for CRIME vulnerability (TLS compression issue) -B, --breach tests for BREACH vulnerability (HTTP compression issue) @@ -14724,6 +14971,7 @@ initialize_globals() { do_breach=false do_ccs_injection=false do_ticketbleed=false + do_robot=false do_cipher_per_proto=false do_crime=false do_freak=false @@ -14766,6 +15014,7 @@ set_scanning_defaults() { do_heartbleed=true do_ccs_injection=true do_ticketbleed=true + do_robot=true do_crime=true do_freak=true do_logjam=true @@ -14790,7 +15039,7 @@ query_globals() { local true_nr=0 for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ - do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_grease do_renego \ + do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_grease do_robot do_renego \ do_std_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only; do [[ "${!gbl}" == "true" ]] && let true_nr++ @@ -14803,7 +15052,7 @@ debug_globals() { local gbl for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ - do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_grease do_renego \ + do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_grease do_robot do_renego \ do_std_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only; do printf "%-22s = %s\n" $gbl "${!gbl}" @@ -14952,6 +15201,7 @@ parse_cmd_line() { do_heartbleed=true do_ccs_injection=true do_ticketbleed=true + do_robot=true do_renego=true do_crime=true do_breach=true @@ -14978,6 +15228,9 @@ parse_cmd_line() { do_ticketbleed=true let "VULN_COUNT++" ;; + -BB|--robot) + do_robot=true + ;; -R|--renegotiation) do_renego=true let "VULN_COUNT++" @@ -15384,6 +15637,7 @@ lets_roll() { $do_heartbleed && { run_heartbleed; ret=$(($? + ret)); time_right_align run_heartbleed; } $do_ccs_injection && { run_ccs_injection; ret=$(($? + ret)); time_right_align run_ccs_injection; } $do_ticketbleed && { run_ticketbleed; ret=$(($? + ret)); time_right_align run_ticketbleed; } + $do_robot && { run_robot; ret=$(($? + ret)); time_right_align run_robot; } $do_renego && { run_renego; ret=$(($? + ret)); time_right_align run_renego; } $do_crime && { run_crime; ret=$(($? + ret)); time_right_align run_crime; } $do_breach && { run_breach "$URL_PATH" ; ret=$(($? + ret)); time_right_align run_breach; }