- NEW: IP address detection now in HTTP header

- NEW: Varnish and Squid header detected
- NEW: option --ip=one is a shortcut and means just test the first ip
- CSP Report-Only in security headers
- New: Varnish and Squid header detected, OWA header
- all single tests in bold now
- no support for TLS 1.2 spits out "NOT ok" as it is not ok
- Medium ciphers and DES ciphers are not having aNULL and aDH ciphers anymore and have different colors --> ratings
- http-date is now in http header(), tls_time in server_defaults()
- http header reply is indented to same row as server defaults
- http status code is displayed clearly now
- BUGFIX: IPv6 address wasn't displayed
- cleanup
- application banner now in two lines if needed
- try a second time to get a http header if first one fails
- fix: case where % sign in ip address made prinf hiccup (sanitized)
- fix: $url was in some functions empty
- fixed bug where some headers were displayed twice
This commit is contained in:
Dirk 2015-06-19 20:36:32 +02:00
parent 59299ce9e1
commit 633cdc209b

View File

@ -1,7 +1,10 @@
#!/usr/bin/env bash
#
# vim:ts=5:sw=5
# use vim and you will see everything beautifully indented with a 5 char tab
[ -z "$BASH_VERSINFO" ] && echo "\n$(tput setaf 5) Please make sure you're using bash! Bye...$(tput sgr0)\n" && exit 1
#
# testssl.sh is a program for spotting weak SSL encryption, ciphers, version and some
# vulnerabilities or features
#
@ -463,6 +466,10 @@ runs_HTTP() {
return $ret
}
strip_lf() {
echo "$1" | tr -d '\n' | tr -d '\r'
}
#problems not handled: chunked
http_header() {
@ -470,9 +477,8 @@ http_header() {
local -i ret
local referer useragent
local url
local redir2
outln; pr_blue "--> Testing HTTP header response"; outln "\n"
outln; pr_blue "--> Testing HTTP header response"; outln " @ \"$URL_PATH\"\n"
[ -z "$1" ] && url="/" || url="$1"
if [ $SNEAKY -eq 0 ] ; then
@ -508,63 +514,109 @@ EOF
mv $HEADERFILE.2 $HEADERFILE # sed'ing in place doesn't work with BSD and Linux simultaneously
ret=0
else
pr_litemagentaln " failed (HTTP header request stalled)"
ret=3
fi
if egrep -aq "^HTTP.1.. 301|^HTTP.1.. 302|^Location" $HEADERFILE; then
redir2=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n')
outln " (got 30x to $redir2 - may be better try this URL?)\n"
elif egrep -aq "^HTTP.1.. 401|^WWW-Authenticate" $HEADERFILE; then
outln " (got 401 / WWW-Authenticate, can't look beyond it)\n"
elif egrep -aq "^HTTP.1.. 400 Bad Request" $HEADERFILE; then
pr_litemagentaln " (got \"400 Bad Request\": GET request was somehow wrong)\n"
fi
[[ $DEBUG -eq 0 ]] && rm $HEADERFILE.2 2>/dev/null
return $ret
}
#problems not handled: chunked
NEW_http_header() {
local header
local -i ret
local referer useragent
local url
local redir2
outln; pr_blue "--> Testing HTTP header response"; outln "\n"
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO -quiet -ign_eof -connect $NODEIP:$PORT $SNI &>$HEADERFILE &
pid=$!
if wait_kill $pid $HEADER_MAXSLEEP; then
if ! egrep -iaq "XML|HTML|DOCTYPE|HTTP|Connection" $HEADERFILE; then
pr_litemagenta " likely HTTP header requests failed (#lines: $(wc -l < $HEADERFILE | sed 's/ //g'))."
outln "Rerun with DEBUG=1 and inspect \"http_header.txt\"\n"
debugme cat $HEADERFILE
ret=7
#TODO: attention: if this runs into a timeout, we're dead. Try again differently:
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO -quiet -ign_eof -connect $NODEIP:$PORT $SNI &>$HEADERFILE
if [ $? -ne 0 ]; then
pr_litemagentaln " failed (HTTP header request stalled)"
return 3
#ret=3
fi
sed -e '/^<HTML/,$d' -e '/^<html/,$d' -e '/^<XML /,$d' -e '/<?XML /,$d' \
-e '/^<xml /,$d' -e '/<?xml /,$d' -e '/^<\!DOCTYPE/,$d' -e '/^<\!doctype/,$d' $HEADERFILE >$HEADERFILE.2
#TODO ^^^ Attention: the filtering for the html body only as of now, doesn't work for other content yet
mv $HEADERFILE.2 $HEADERFILE # sed'ing in place doesn't work with BSD and Linux simultaneously
ret=0
else
pr_litemagentaln " failed (HTTP header request stalled)"
ret=3
fi
if egrep -aq "^HTTP.1.. 301|^HTTP.1.. 302|^Location" $HEADERFILE; then
redir2=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n')
outln " (got 30x to $redir2 - may be better try this URL?)\n"
elif egrep -aq "^HTTP.1.. 401|^WWW-Authenticate" $HEADERFILE; then
outln " (got 401 / WWW-Authenticate, can't look beyond it)\n"
elif egrep -aq "^HTTP.1.. 400 Bad Request" $HEADERFILE; then
pr_litemagentaln " (got \"400 Bad Request\": GET request was somehow wrong)\n"
fi
[[ $DEBUG -eq 0 ]] && rm $HEADERFILE.2 2>/dev/null
status_code=$(awk '/^HTTP\// { print $2 }' $HEADERFILE)
msg_thereafter=$(awk -F"$status_code" '/^HTTP\// { print $2 }' $HEADERFILE) # dirty trick to use the status code as a
msg_thereafter=$(strip_lf "$msg_thereafter") # field separator, otherwise we need a loop with awk
debugme echo "Status/MSG: $status_code $msg_thereafter"
pr_bold " HTTP Status Code "
[ -z "$status_code" ] && pr_litemagentaln "No status code" && return 3
out " $status_code$msg_thereafter"
case $status_code in
301|302|307|308) out ", redirecting to \"$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n')\"" ;;
200) ;;
206) out " -- WTF?" ;;
400) pr_litemagenta " (better try another URL)" ;;
401) grep -aq "^WWW-Authenticate" $HEADERFILE && out " "; strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)"
;;
403) ;;
404) out " (Maybe supply a path which doesn't give a \"$status_code$msg_thereafter\")" ;;
405) ;;
*) pr_litemagenta ". Oh, didn't expect a $status_code$msg_thereafter";;
esac
outln
# we don't call "tmpfile_handle $FUNCNAME.txt" as we need the header file in other functions!
return $ret
}
http_date() {
local now difftime
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3 # this is just for the line "Testing HTTP header response"
fi
pr_bold " HTTP clock skew "
if [[ $SERVICE != "HTTP" ]] ; then
out "not tested as we're not targeting HTTP"
else
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO -ign_eof -connect $NODEIP:$PORT $SNI &>$TMPFILE
now=$(date "+%s") # we need an ACCURATE date here and cannot rely on the headerfile!
HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $TMPFILE)
if [ -n "$HTTP_TIME" ] ; then
if $HAS_GNUDATE ; then
HTTP_TIME=$(date --date="$HTTP_TIME" "+%s")
else
HTTP_TIME=$(date -j -f "%a, %d %b %Y %T %Z" "$HTTP_TIME" "+%s" 2>/dev/null) # the trailing \r confuses BSD flavors otherwise
fi
difftime=$(($HTTP_TIME - $now))
[[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
out "$difftime sec from localtime";
else
out "Got no HTTP time, maybe try different URL?";
fi
debugme out "$HTTP_TIME"
fi
outln
detect_ipv4
}
# Borrowd from Glenn Jackman, see https://unix.stackexchange.com/users/4667/glenn-jackman
detect_ipv4() {
local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
local ipv4address="$octet\\.$octet\\.$octet\\.$octet"
local your_ip_msg=" (check if it's yours or e.g. a cluster IP)"
local first=true
local spaces=" "
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
if grep -iqE $ipv4address $HEADERFILE; then
pr_bold " IPv4 address in header "
cat $HEADERFILE | while read line; do
result="$(echo -n "$line" | grep -E $ipv4address )"
result=$(strip_lf "$result")
if [ -n "$result" ] ; then
if ! $first; then
out "$spaces"
your_ip_msg=""
else
first=false
fi
# a little bit of sanitzing, otherwise printf will hiccup @ %
pr_litered "$(echo $result|sed 's/%/%%/g')"
outln "$your_ip_msg"
fi
done
fi
}
includeSubDomains() {
if grep -aiqw includeSubDomains "$1"; then
pr_litegreen ", includeSubDomains"
@ -577,6 +629,7 @@ preload() {
grep -aiqw preload "$1" && pr_litegreen ", preload"
}
hsts() {
local hsts_age_sec
local hsts_age_days
@ -584,7 +637,8 @@ hsts() {
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " HSTS "
#pr_bold " HSTS "
pr_bold " Strict Transport Security "
grep -iaw '^Strict-Transport-Security' $HEADERFILE >$TMPFILE
if [ $? -eq 0 ]; then
grep -aciw '^Strict-Transport-Security' $HEADERFILE | egrep -waq "1" || out "(two HSTS header, using 1st one) "
@ -615,13 +669,14 @@ hpkp() {
local -i hpkp_age_days
local -i hpkp_nr_keys
local hpkp_key hpkp_key_hostcert
local spaces=" "
local spaces=" "
local -i key_found=1
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " HPKP "
#pr_bold " HPKP "
pr_bold " Public Key Pinning "
egrep -aiw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE >$TMPFILE
if [ $? -eq 0 ]; then
egrep -aciw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE | egrep -waq "1" || out "(two HPKP headers, using 1st one) "
@ -686,6 +741,8 @@ emphasize_stuff_in_headers(){
-e "s/Via/"$yellow"Via$off/g" \
-e "s/X-Cache-Lookup/"$yellow"X-Cache-Lookup$off/g" \
-e "s/X-Cache/"$yellow"X-Cache$off/g" \
-e "s/X-Squid/"$yellow"X-Squid$off/g" \
-e "s/X-Varnish/"$yellow"X-Varnish$off/g" \
-e "s/X-OWA-Version/"$yellow"X-OWA-Version$off/g" \
-e "s/X-Version/"$yellow"X-Version$off/g" \
-e "s/X-Powered-By/"$yellow"X-Powered-By$off/g" \
@ -698,7 +755,7 @@ server_banner() {
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Server "
pr_bold " Server banner "
grep -ai '^Server' $HEADERFILE >$TMPFILE
if [ $? -eq 0 ]; then
serverbanner=$(sed -e 's/^Server: //' -e 's/^server: //' $TMPFILE)
@ -724,25 +781,38 @@ rp_banner() {
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Reverse Proxy "
egrep -ai '^Via|^X-Cache' $HEADERFILE >$TMPFILE && \
pr_bold " Reverse Proxy banner "
egrep -ai '^Via|^X-Cache|^X-Squid|X-Varnish' $HEADERFILE >$TMPFILE && \
emphasize_stuff_in_headers "$(sed 's/^/ /g' $TMPFILE | tr '\n\r' ' ')" || \
outln " --"
outln "--"
tmpfile_handle $FUNCNAME.txt
return 0
}
application_banner() {
local line
local first=true
local spaces=" "
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Application "
pr_bold " Application banner "
egrep -ai '^X-Powered-By|^X-AspNet-Version|^X-Version|^Liferay-Portal|^X-OWA-Version' $HEADERFILE >$TMPFILE
[ $? -ne 0 ] && \
outln " (no banner at \"$url\")" || \
emphasize_stuff_in_headers "$(sed 's/^/ /g' $TMPFILE | tr '\n\r' ' ')"
if [ $? -ne 0 ] ; then
outln "--"
else
cat $TMPFILE | while read line; do
line=$(strip_lf "$line")
if ! $first; then
out "$spaces"
else
first=false
fi
emphasize_stuff_in_headers "$line"
done
fi
tmpfile_handle $FUNCNAME.txt
return 0
}
@ -755,7 +825,7 @@ cookie_flags() { # ARG1: Path, ARG2: path
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Cookie(s) "
pr_bold " Cookie(s) "
grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE
if [ $? -eq 0 ]; then
nr_cookies=$(wc -l < $TMPFILE | sed 's/ //g')
@ -778,7 +848,7 @@ cookie_flags() { # ARG1: Path, ARG2: path
esac
out " HttpOnly"
else
out "(none issued at \"$url\")"
out "(none issued at \"$1\")"
fi
outln
@ -788,40 +858,43 @@ cookie_flags() { # ARG1: Path, ARG2: path
more_flags() {
local good_flags2test="X-Frame-Options X-XSS-Protection X-Content-Type-Options Content-Security-Policy X-Content-Security-Policy X-WebKit-CSP"
local good_flags2test="X-Frame-Options X-XSS-Protection X-Content-Type-Options Content-Security-Policy X-Content-Security-Policy X-WebKit-CSP Content-Security-Policy-Report-Only"
local other_flags2test="Access-Control-Allow-Origin Upgrade X-Served-By"
local egrep_pattern=""
local f2t result_str
local blanks=" "
local first=true
local spaces=" "
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Security headers "
egrep_pattern=$(echo "$good_flags2test $other_flags2test"| sed -e 's/ /|\^/g' -e 's/^/\^/g') # space -> |^
pr_bold " Security headers "
# convert spaces to | (for egrep)
egrep_pattern=$(echo "$good_flags2test $other_flags2test"| sed -e 's/ /|\^/g' -e 's/^/\^/g')
egrep -ai "$egrep_pattern" $HEADERFILE >$TMPFILE
if [ $? -ne 0 ]; then
outln "(none at \"$url\")"
outln "--"
ret=1
else
ret=0
first=true
for f2t in $good_flags2test; do
result_str=$(grep -i "^$f2t" $TMPFILE)
debugme "---> $f2t"
result_str=$(grep -wi "^$f2t" $TMPFILE | grep -vi "$f2t"-)
result_str=$(strip_lf "$result_str")
[ -z "$result_str" ] && continue
if ! $first; then
out "$blanks" # output leading spaces if the first header
out "$spaces" # output leading spaces if the first header
else
first=false
fi
if [ $(echo "$result_str" | wc -l | sed 's/ //g') -eq 1 ]; then
#if [ $(echo "$result_str" | wc -l | sed 's/ //g') -eq 1 ]; then
pr_litegreenln "$result_str"
else # for the case we have two times the same header:
#else # for the case we have two times the same header:
# exchange the line feeds between the two lines only:
pr_litecyan "double -->" ; echo "$result_str" | tr '\n\r' ' | ' | sed 's/| $//g'
pr_litecyanln "<-- double"
#pr_litecyan "double -->" ; echo "$result_str" | tr '\n\r' ' | ' | sed 's/| $//g'
#pr_litecyanln "<-- double"
#FIXME: https://report-uri.io has double here
fi
#fi
done
# now the same with other flags
for f2t in $other_flags2test; do
@ -904,13 +977,12 @@ listciphers() {
# argv[2]: string on console
# argv[3]: ok to offer? 0: yes, 1: no
std_cipherlists() {
out "$2 ";
pr_bold "$2 " # indent in order to be in the same row as server preferences
if listciphers $1; then # is that locally available??
[ $SHOW_LOC_CIPH -eq 0 ] && out "local ciphers are: " && sed 's/:/, /g' $TMPFILE
$OPENSSL s_client -cipher "$1" $STARTTLS -connect $NODEIP:$PORT $SNI 2>$TMPFILE >/dev/null </dev/null
ret=$?
[[ $DEBUG -ge 2 ]] && cat $TMPFILE
out " " # in order to be in the same row as server preferences
case $3 in
0) # ok to offer
[[ $ret -eq 0 ]] && \
@ -1182,6 +1254,8 @@ testprotohelper() {
runprotocols() {
local using_sockets=0
local supported_no_ciph1="supported but couldn't detect a cipher (may need debugging)"
local supported_no_ciph2="supported but couldn't detect a cipher"
pr_blue "--> Testing protocols ";
@ -1192,20 +1266,21 @@ runprotocols() {
outln "(via sockets for SSLv2, SSLv3)\n"
fi
out " SSLv2 ";
pr_bold " SSLv2 ";
if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ]; then
testprotohelper "-ssl2"
case $? in
0) pr_redln "offered (NOT ok)" ;;
1) pr_greenln "not offered (OK)" ;;
5) pr_litered "supported but couldn't detect a cipher"; outln "(may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
5) pr_litered "$supported_no_ciph2";
outln "(may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
else
sslv2_sockets #FIXME: --> Umschreiben, Interpretation mit CASE wie native
sslv2_sockets #FIXME: --> Umschreiben, Interpretation mit CASE wie native
fi
out " SSLv3 ";
pr_bold " SSLv3 ";
if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ]; then
testprotohelper "-ssl3"
else
@ -1215,40 +1290,41 @@ runprotocols() {
0) pr_literedln "offered (NOT ok)" ;;
1) pr_greenln "not offered (OK)" ;;
2) pr_magentaln "#FIXME: downgraded. still missing a test case here" ;;
5) pr_litered "supported but couldn't detect a cipher"; outln "(may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
5) pr_litered "$supported_no_ciph2";
outln "(may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
out " TLS 1 ";
pr_bold " TLS 1 ";
#if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ]; then
testprotohelper "-tls1"
#else
#tls_sockets "01" "$TLS_CIPHER"
#fi
case $? in
0) outln "offered" ;; # no GCM, thus only normal print
1) outln "not offered" ;; # neither good or bad
0) outln "offered" ;; # nothing wrong with it -- per se
1) outln "not offered" ;; # neither good or bad
2) pr_magentaln "downgraded. still missing a testcase here" ;;
5) outln "supported but couldn't detect a cipher (may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
5) outln "$supported_no_ciph1" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
out " TLS 1.1 ";
pr_bold " TLS 1.1 ";
testprotohelper "-tls1_1"
case $? in
0) outln "offered" ;; # normal print
1) outln "not offered" ;; # neither good or bad
5) outln "supported but couldn't detect a cipher (may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
0) outln "offered" ;; # nothing wrong with it
1) outln "not offered" ;; # neither good or bad
5) outln "$supported_no_ciph1" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
out " TLS 1.2 ";
pr_bold " TLS 1.2 ";
testprotohelper "-tls1_2"
case $? in
0) pr_greenln "offered (OK)" ;; # GCM cipher in TLS 1.2: very good!
1) pr_brownln "not offered" ;; # no GCM, penalty
5) outln "supported but couldn't detect a cipher (may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
0) pr_greenln "offered (OK)" ;; # GCM cipher in TLS 1.2: very good!
1) pr_brownln "not offered (NOT ok)" ;; # no GCM, penalty
5) outln "$supported_no_ciph1" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
return 0
@ -1331,7 +1407,7 @@ server_preference() {
outln;
pr_blue "--> Testing server preferences"; outln "\n"
out " Has server cipher order? "
pr_bold " Has server cipher order? "
$OPENSSL s_client $STARTTLS -cipher $list1 -connect $NODEIP:$PORT $SNI </dev/null 2>/dev/null >$TMPFILE
if [ $? -ne 0 ]; then
pr_magenta "no matching cipher in this list found (pls report this): "
@ -1354,7 +1430,7 @@ server_preference() {
outln
$OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT $SNI </dev/null 2>/dev/null >$TMPFILE
out " Negotiated protocol "
pr_bold " Negotiated protocol "
default_proto=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g')
case "$default_proto" in
*TLSv1.2) pr_greenln $default_proto ;;
@ -1366,7 +1442,7 @@ server_preference() {
*) outln "$default_proto" ;;
esac
out " Negotiated cipher "
pr_bold " Negotiated cipher "
default_cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g')
case "$default_cipher" in
*NULL*|*EXP*) pr_red "$default_cipher" ;;
@ -1382,7 +1458,7 @@ server_preference() {
outln "$remark4default_cipher"
if [ ! -z "$remark4default_cipher" ]; then
out " Negotiated cipher per proto $remark4default_cipher"
pr_bold " Negotiated cipher per proto"; out " $remark4default_cipher"
i=1
for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do
locally_supported -"$p" || continue
@ -1444,7 +1520,7 @@ cipher_pref_check() {
local p proto protos
local tested_cipher cipher
out " Cipher order"
pr_bold " Cipher order"
for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do
$OPENSSL s_client $STARTTLS -"$p" -connect $NODEIP:$PORT $SNI </dev/null 2>/dev/null >$TMPFILE
@ -1500,10 +1576,38 @@ get_host_cert() {
}
tls_time() {
local now difftime
if [ -n "$STARTTLS" ] ; then
pr_bold " TLS timestamp"; outln " (not yet implemented for STARTTLS) "
else
tls_sockets "01" "$TLS_CIPHER" # try first TLS 1.0
[[ -z "$TLS_TIME" ]] && tls_sockets "03" "$TLS12_CIPHER" # TLS 1.2
[[ -z "$TLS_TIME" ]] && tls_sockets "02" "$TLS_CIPHER" # TLS 1.1
[[ -z "$TLS_TIME" ]] && tls_sockets "00" "$TLS_CIPHER" # SSL 3
# TODO: maybe too much tests -- timing !
if [[ -n "$TLS_TIME" ]]; then # nothing returned a time!
difftime=$(($TLS_TIME - $TLS_NOW))
if [[ "${#difftime}" -gt 5 ]]; then
# openssl >= 1.0.1f fills this field with random values! --> good for possible fingerprint
pr_bold " TLS timestamp" ; outln " random values, no fingerprinting possible "
else
[[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
pr_bold " TLS clock skew" ; outln " $difftime sec from localtime";
fi
debugme out "$TLS_TIME"
outln
else
pr_bold " TLS timestamp" ; outln " "; pr_litemagentaln "SSLv3 through TLS 1.2 didn't return a timestamp"
fi
fi
}
server_defaults() {
local proto
local gost_status_problem=false
local now difftime
local extensions
local sessticket_str lifetime unit keysize algo
local expire ocsp_uri crl savedir startdate enddate issuer_c issuer_o issuer sans san cn cn_nosni
@ -1511,56 +1615,6 @@ server_defaults() {
outln
pr_blue "--> Testing server defaults (Server Hello)"; outln "\n"
# first TLS time:
if [ -n "$STARTTLS" ] ; then
outln " TLS timestamp: (not yet implemented for STARTTLS) "
else
tls_sockets "03" "$TLS12_CIPHER"
[ -z "$TLS_TIME" ] && tls_sockets "02" "$TLS_CIPHER"
[ -z "$TLS_TIME" ] && tls_sockets "01" "$TLS_CIPHER"
[ -z "$TLS_TIME" ] && tls_sockets "00" "$TLS_CIPHER"
if [ -n "$TLS_TIME" ]; then
difftime=$(($TLS_TIME - $TLS_NOW))
if [[ "${#difftime}" -gt 5 ]]; then
# openssl >= 1.0.1f fills this field with random values
out " TLS timestamp: random values, no fingerprinting possible "
else
[[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
out " TLS clock skew: $difftime sec from localtime";
fi
debugme out "$TLS_TIME"
outln
else
out " TLS timestamp: "; pr_litemagentaln "SSLv3 through TLS 1.2 didn't return a timestamp"
fi
fi
# HTTP date:
out " HTTP clock skew: "
if [[ $SERVICE != "HTTP" ]] ; then
out "not tested as we're not targeting HTTP"
else
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO -ign_eof -connect $NODEIP:$PORT $SNI &>$TMPFILE
now=$(date "+%s")
HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $TMPFILE)
if [ -n "$HTTP_TIME" ] ; then
if $HAS_GNUDATE ; then
HTTP_TIME=$(date --date="$HTTP_TIME" "+%s")
else
HTTP_TIME=$(date -j -f "%a, %d %b %Y %T %Z" "$HTTP_TIME" "+%s" 2>/dev/null) # the trailing \r confuses BSD flavors otherwise
fi
difftime=$(($HTTP_TIME - $now))
[[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
out "$difftime sec from localtime";
else
out "Got no HTTP time, maybe try different URL?";
fi
debugme out "$HTTP_TIME"
fi
outln
#TLS extensions follow now
# throwing 1st every cipher/protocol at the server to know what works
for proto in tls1_2 tls1_1 tls1 ssl3; do
@ -1581,7 +1635,7 @@ server_defaults() {
fi
fi
$OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT -$proto 2>/dev/null </dev/null | awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT.nosni
out " TLS server extensions "
pr_bold " TLS server extensions "
extensions=$(grep -aw "^TLS server extension" $TMPFILE | sed -e 's/^TLS server extension \"//' -e 's/\".*$/,/g')
if [ -z "$extensions" ]; then
outln "(none)"
@ -1589,7 +1643,7 @@ server_defaults() {
echo $extensions | sed 's/,$//' # remove last comma
fi
out " Session Tickets RFC 5077 "
pr_bold " Session Tickets RFC 5077 "
sessticket_str=$(grep -aw "session ticket" $TMPFILE | grep -a lifetime)
if [ -z "$sessticket_str" ]; then
outln "(none)"
@ -1599,7 +1653,7 @@ server_defaults() {
outln "$lifetime $unit"
fi
out " Server key size "
pr_bold " Server key size "
keysize=$(grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //')
if [ -z "$keysize" ]; then
outln "(couldn't determine)"
@ -1619,7 +1673,7 @@ server_defaults() {
outln " bit"
#FIXME: google seems to have EC keys which displays as 256 Bit
out " Signature Algorithm "
pr_bold " Signature Algorithm "
algo=$($OPENSSL x509 -in $HOSTCERT -noout -text | grep "Signature Algorithm" | sed 's/^.*Signature Algorithm: //' | sort -u )
case $algo in
sha1WithRSAEncryption) pr_brownln "SHA1 with RSA" ;;
@ -1631,11 +1685,11 @@ server_defaults() {
esac
# old, but interesting: https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html
out " Fingerprint / Serial "
pr_bold " Fingerprint / Serial "
outln "$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 | sed 's/Fingerprint=//' | sed 's/://g' ) / $($OPENSSL x509 -noout -in $HOSTCERT -serial | sed 's/serial=//')"
outln " $($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 | sed 's/Fingerprint=//' | sed 's/://g' )"
out " Common Name (CN) "
pr_bold " Common Name (CN) "
cn=$($OPENSSL x509 -in $HOSTCERT -noout -subject | sed 's/subject= //' | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//')
pr_underline "$cn"
@ -1657,8 +1711,9 @@ server_defaults() {
sans=$($OPENSSL x509 -in $HOSTCERT -noout -text | grep -A3 "Subject Alternative Name" | grep "DNS:" | \
sed -e 's/DNS://g' -e 's/ //g' -e 's/,/\n/g' -e 's/othername:<unsupported>//g')
# ^^^ CACert
out " subjectAltName (SAN) "
# ^^^ CACert
pr_bold " subjectAltName (SAN) "
if [ -n "$sans" ]; then
sans=$(echo "$sans" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g') # replace line feed by " "
for san in $sans; do
@ -1668,7 +1723,7 @@ server_defaults() {
out "-- "
fi
outln
out " Issuer "
pr_bold " Issuer "
issuer=$($OPENSSL x509 -in $HOSTCERT -noout -issuer | sed -e 's/^.*CN=//g' -e 's/\/.*$//g')
issuer_o=$($OPENSSL x509 -in $HOSTCERT -noout -issuer | sed 's/^.*O=//g' | sed 's/\/.*$//g')
if $OPENSSL x509 -in $HOSTCERT -noout -issuer | grep -q 'C=' ; then
@ -1684,7 +1739,7 @@ server_defaults() {
outln "$underline$issuer$off ($underline$issuer_o$off from $underline$issuer_c$off)"
fi
out " Certificate Expiration "
pr_bold " Certificate Expiration "
expire=$($OPENSSL x509 -in $HOSTCERT -checkend 0)
if ! echo $expire | grep -qw not; then
pr_red "expired!"
@ -1718,23 +1773,23 @@ server_defaults() {
$OPENSSL s_client -showcerts $STARTTLS -connect $NODEIP:$PORT $SNI 2>/dev/null </dev/null | \
awk -v c=-1 '/-----BEGIN CERTIFICATE-----/{inc=1;c++} inc {print > ("level" c ".crt")} /---END CERTIFICATE-----/{inc=0}'
nrsaved=$(ls $TEMPDIR/level?.crt 2>/dev/null | wc -w | sed 's/^ *//')
outln " # of certificates provided $nrsaved"
pr_bold " # of certificates provided"; outln " $nrsaved"
cd "$savedir"
out " Certificate Revocation List "
pr_bold " Certificate Revocation List "
crl=$($OPENSSL x509 -in $HOSTCERT -noout -text | grep -A 4 "CRL Distribution" | grep URI | sed 's/^.*URI://')
[ x"$crl" == "x" ] && pr_literedln "--" || echo "$crl"
out " OCSP URI "
pr_bold " OCSP URI "
ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri)
[ x"$ocsp_uri" == "x" ] && pr_literedln "--" || echo "$ocsp_uri"
out " OCSP stapling "
pr_bold " OCSP stapling "
if grep "OCSP response" $TMPFILE | grep -q "no response sent" ; then
out " not offered"
else
if grep "OCSP Response Status" $TMPFILE | grep -q successful; then
pr_litegreen " OCSP stapling offered"
pr_litegreen " offered"
else
if [ $gost_status_problem = "true" ]; then
outln " (GOST servers make problems here, sorry)"
@ -1748,7 +1803,12 @@ server_defaults() {
fi
outln
# if we call tls_time before tmpfile_handle it throws an error because the function tls_sockets removed $TMPFILE
# already -- and that was a different one -- means that would get overwritten anyway
tmpfile_handle tlsextdebug+status.txt
tls_time
return $ret
}
# FIXME: revoked, see checkcert.sh
@ -1860,7 +1920,7 @@ spdy_pre(){
}
spdy() {
out " SPDY/NPN "
pr_bold " SPDY/NPN "
if ! spdy_pre ; then
echo
return 0
@ -1873,7 +1933,7 @@ spdy() {
else
# now comes a strange thing: "Protocols advertised by server:" is empty but connection succeeded
if echo $tmpstr | egrep -aq "spdy|http" ; then
pr_bold "$tmpstr" ; out " (advertised)"
out "$tmpstr" ; out " (advertised)"
ret=0
else
pr_litemagenta "please check manually, server response was ambigious ..."
@ -2651,6 +2711,11 @@ crime() {
# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic
# to the version of TLS/SSL, more: http://www.breachattack.com/ . Foreign referrers are the important thing here!
breach() {
local header
local -i ret
local referer useragent
local url
[[ $SERVICE != "HTTP" ]] && return 7
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for BREACH (HTTP compression) vulnerability" && outln "\n"
@ -2851,7 +2916,7 @@ logjam() {
$OPENSSL s_client $STARTTLS -cipher $exportdhe_cipher_list -connect $NODEIP:$PORT $SNI &>$TMPFILE </dev/null
ret=$?
[ "$VERBERR" -eq 0 ] && egrep -a "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
addtl_warning="$addtl_warning, precomputable primes not checked. \"$PROG_NAME -E\" spots candidates"
addtl_warning="$addtl_warning, common primes not checked. \"$PROG_NAME -E\" spots candidates"
if [ $ret -eq 0 ]; then
pr_red "VULNERABLE (NOT ok)"; out ", uses DHE EXPORT ciphers"
else
@ -3200,7 +3265,8 @@ $PROG_NAME <options> URI ("$PROG_NAME URI" does everything except ciphers per
<-t|--starttls> protocol does a default run against a STARTTLS enabled service
<--mx> domain/host tests MX records from high to low priority (STARTTLS, port 25)
<--ip> ipv4 takes ipv4 instead of resolving host in URI (supplied host name is vhost then)
<--ip> ipv4 tests the one supplied ipv4 instead of resolving host(s) in URI
"one" means: just test the first DNS returns (useful for multiple IPs)
partly mandatory parameters:
@ -3489,7 +3555,7 @@ determine_ip_addresses() {
exit -1
fi
[[ ! -z "$ip6" ]] && IP46ADDRs="$ip4 $ip6" || IP46ADDRs="$IPADDRs"
IP46ADDRs=$(newline_to_spaces "$IP46ADDR")
IP46ADDRs=$(newline_to_spaces "$IP46ADDRs")
return 0 # IPADDR and IP46ADDR is set now
}
@ -3970,6 +4036,8 @@ lets_roll() {
if ${do_header}; then
#TODO: refactor this into functions
if [[ $SERVICE == "HTTP" ]]; then
http_header "$URL_PATH"
http_date "$URL_PATH"
hsts "$URL_PATH"
hpkp "$URL_PATH"
server_banner "$URL_PATH"
@ -4032,8 +4100,10 @@ if ${do_mx_all_ips} ; then
else
parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now
determine_ip_addresses
if [[ -n "$CMDLINE_IP" ]]; then
NODEIP="$CMDLINE_IP" # specific ip address for NODE was supplied
if [[ -n "$CMDLINE_IP" ]]; then
[[ "$CMDLINE_IP" == "one" ]] && \
CMDLINE_IP=$(echo -n "$IPADDRs" | awk '{ print $1 }') || \
NODEIP="$CMDLINE_IP" # specific ip address for NODE was supplied
lets_roll "${STARTTLS_PROTOCOL}"
ret=$?
else # no --ip was supplied
@ -4060,6 +4130,4 @@ fi
exit $ret
# $Id: testssl.sh,v 1.279 2015/06/17 09:33:28 dirkw Exp $
# vim:ts=5:sw=5
# ^^^ FYI: use vim and you will see everything beautifully indented with a 5 char tab
# $Id: testssl.sh,v 1.285 2015/06/19 18:34:00 dirkw Exp $