From ec4ceb2c20598a98b5616e2b5ad4552a6064b037 Mon Sep 17 00:00:00 2001
From: Maurizio S <46047144+akabe1@users.noreply.github.com>
Date: Sat, 20 Jan 2024 11:49:05 +0100
Subject: [PATCH 1/5] Add mTLS feature
Added new feature to support mutual TLS via client certificate and private key, when a remote server requires client authentication.
---
testssl.sh | 81 ++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 63 insertions(+), 18 deletions(-)
diff --git a/testssl.sh b/testssl.sh
index fb689df..4c50f24 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -389,6 +389,7 @@ XMPP_HOST=""
PROXYIP="" # $PROXYIP:$PROXPORT is your proxy if --proxy is defined ...
PROXYPORT="" # ... and openssl has proxy support
PROXY="" # Once check_proxy() executed it contains $PROXYIP:$PROXPORT
+MTLS="" # mTLS authentication with client certificate and private key
VULN_COUNT=0
SERVICE="" # Is the server running an HTTP server, SMTP, POP or IMAP?
URI=""
@@ -2316,6 +2317,12 @@ s_client_options() {
fi
# $keyopts may be set as an environment variable to enable client authentication (see PR #1383)
tm_out "$options $keyopts"
+
+ # In case of mutual TLS authentication is required by the server
+ # Note: the PEM certificate file must contain: client certificate and certificate key (not encrypted)
+ if [[ -n "$MTLS" ]]; then
+ options+=" -cert $MTLS"
+ fi
}
###### check code starts here ######
@@ -2375,10 +2382,14 @@ service_detection() {
out " $SERVICE, thus skipping HTTP specific checks"
fileout "${jsonID}" "INFO" "$SERVICE, thus skipping HTTP specific checks"
;;
- *) if [[ "$CLIENT_AUTH" == required ]]; then
- out " certificate-based authentication => skipping all HTTP checks"
- echo "certificate-based authentication => skipping all HTTP checks" >$TMPFILE
- fileout "${jsonID}" "INFO" "certificate-based authentication => skipping all HTTP checks"
+ *) if [[ ! -z $MTLS ]]; then
+ out " not identified, but mTLS authentication is set ==> trying HTTP checks"
+ SERVICE=HTTP
+ fileout "${jsonID}" "DEBUG" "Couldn't determine service -- ASSUME_HTTP set"
+ elif [[ "$CLIENT_AUTH" == required ]] && [[ -z $MTLS ]]; then
+ out " certificate-based authentication without providing client certificate and private key => skipping all HTTP checks"
+ echo "certificate-based authentication without providing client certificate and private key => skipping all HTTP checks" >$TMPFILE
+ fileout "${jsonID}" "INFO" "certificate-based authentication without providing client certificate and private key => skipping all HTTP checks"
else
out " Couldn't determine what's running on port $PORT"
if "$ASSUME_HTTP"; then
@@ -2430,6 +2441,7 @@ run_http_header() {
local url redirect
local jsonID="HTTP_status_code"
local spaces=" "
+ local cert_option=""
HEADERFILE=$TEMPDIR/$NODEIP.http_header.txt
if [[ $NR_HEADER_FAIL -eq 0 ]]; then
@@ -2444,12 +2456,17 @@ run_http_header() {
pr_bold " HTTP Status Code "
[[ -z "$1" ]] && url="/" || url="$1"
- tm_out "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE &
+
+ # Set -cert option value if mTLS authentication is selected
+ if [[ ! -z "$MTLS" ]]; then
+ cert_option="-cert $MTLS"
+ fi
+ tm_out "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS $cert_option -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE &
wait_kill $! $HEADER_MAXSLEEP
if [[ $? -eq 0 ]]; then
# Issue HTTP GET again as it properly finished within $HEADER_MAXSLEEP and didn't hang.
# Doing it again in the foreground to get an accurate header time
- tm_out "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE
+ tm_out "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS $cert_option -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE
NOW_TIME=$(date "+%s")
HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $HEADERFILE)
HTTP_AGE=$(awk -F': ' '/^[aA][gG][eE]: / { print $2 }' $HEADERFILE)
@@ -2601,7 +2618,7 @@ run_http_date() {
local spaces=" "
jsonID="HTTP_clock_skew"
- if [[ $SERVICE != HTTP ]] || [[ "$CLIENT_AUTH" == required ]]; then
+ if [[ $SERVICE != HTTP ]] || { [[ "$CLIENT_AUTH" == required ]] && [[ -z "$MTLS" ]]; }; then
return 0
fi
if [[ ! -s $HEADERFILE ]]; then
@@ -6710,6 +6727,12 @@ sub_session_resumption() {
local sess_data=$(mktemp $TEMPDIR/sub_session_data_resumption.$NODEIP.XXXXXX)
local -a rw_line
local protocol="$1"
+ local cert_option=""
+
+ # Set -cert option value if mTLS authentication is selected
+ if [[ ! -z "$MTLS" ]]; then
+ cert_option="-cert $MTLS"
+ fi
if [[ "$2" == ID ]]; then
local byID=true
@@ -6721,7 +6744,8 @@ sub_session_resumption() {
return 1
fi
fi
- [[ "$CLIENT_AUTH" == required ]] && return 6
+ # Return 6 if client authentication is required and none PEM file (containing client certificate+private key) is provided
+ [[ "$CLIENT_AUTH" == required ]] && [[ -z "$MTLS" ]] && return 6
if ! "$HAS_TLS13" && "$HAS_NO_SSL2"; then
addcmd+=" -no_ssl2"
else
@@ -6738,7 +6762,7 @@ sub_session_resumption() {
addcmd+=" $protocol"
fi
- $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_out $sess_data") $tmpfile
+ $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $cert_option $addcmd -sess_out $sess_data") $tmpfile
ret1=$?
if [[ $ret1 -ne 0 ]]; then
# MacOS and LibreSSL return 1 here, that's why we need to check whether the handshake contains e.g. a certificate
@@ -6756,7 +6780,7 @@ sub_session_resumption() {
# [[ ! $(<$sess_data) =~ -----.*\ SSL\ SESSION\ PARAMETERS----- ]]
ret=2
else
- $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_in $sess_data") $tmpfile 2>$ERRFILE
+ $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $cert_option $addcmd -sess_in $sess_data") $tmpfile 2>$ERRFILE
ret2=$?
if [[ $DEBUG -ge 2 ]]; then
echo -n "$ret1, $ret2, "
@@ -17037,9 +17061,9 @@ run_renego() {
[[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers"
outln
fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe"
- elif [[ "$CLIENT_AUTH" == required ]]; then
- prln_warning "client x509-based authentication prevents this from being tested"
- fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested"
+ elif [[ "$CLIENT_AUTH" == required ]] && [[ -z "$MTLS" ]]; then
+ prln_warning "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested"
+ fileout "$jsonID" "WARN" "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested"
sec_client_renego=1
else
# We will need $ERRFILE for mitigation detection
@@ -17203,7 +17227,7 @@ run_crime() {
fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
fi
else
- if [[ $SERVICE == HTTP ]] || [[ "$CLIENT_AUTH" == required ]]; then
+ if [[ $SERVICE == HTTP ]] || [[ "$CLIENT_AUTH" == required ]] || [[ ! -z "$MTLS" ]]; then
pr_svrty_high "VULNERABLE (NOT ok)"
fileout "$jsonID" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint"
else
@@ -17262,8 +17286,13 @@ sub_breach_helper() {
local get_command="$1"
local detected_compression=""
local -i was_killed=0
+ local cert_option=""
- safe_echo "$get_command" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE &
+ # Set -cert option value if mTLS authentication is selected
+ if [[ ! -z "$MTLS" ]]; then
+ cert_option="-cert $MTLS"
+ fi
+ safe_echo "$get_command" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS $cert_option -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE &
wait_kill $! $HEADER_MAXSLEEP
was_killed=$? # !=0 when it was killed
detected_compression=$(grep -ia ^Content-Encoding: $TMPFILE)
@@ -17313,9 +17342,9 @@ run_breach() {
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln
pr_bold " BREACH"; out " ($cve) "
- if [[ "$CLIENT_AUTH" == required ]]; then
- prln_warning "client x509-based authentication prevents this from being tested"
- fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" "$cve" "$cwe"
+ if [[ "$CLIENT_AUTH" == required ]] && [[ -z "$MTLS" ]]; then
+ prln_warning "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested"
+ fileout "$jsonID" "WARN" "not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested" "$cve" "$cwe"
return 7
fi
@@ -20500,6 +20529,7 @@ tuning / connect options (most also can be preset via environment variables):
--ids-friendly skips a few vulnerability checks which may cause IDSs to block the scanning IP
--phone-out allow to contact external servers for CRL download and querying OCSP responder
--add-ca WARNINGS
--reqheader <header>
This can be used to add additional HTTP request headers in the correct format Headername: headercontent
. This parameter can be called multiple times if required. For example: --reqheader 'Proxy-Authorization: Basic dGVzdHNzbDpydWxlcw==' --reqheader 'ClientID: 0xDEADBEAF'
. REQHEADER is the corresponding environment variable.
--mtls <path_to_client_cert>
This can be set to provide a file containing a client certificatete and a private key (not encrypted) in PEM format, which is used when a mutual TLS authentication is required by the remote server. MTLS is the is the equivalent environment variable.
-t <protocol>, --starttls <protocol>
does a default run against a STARTTLS enabled protocol
. protocol
must be one of ftp
, smtp
, pop3
, imap
, xmpp
, sieve
, xmpp-server
, telnet
, ldap
, irc
, lmtp
, nntp
, postgres
, mysql
. For the latter four you need e.g. the supplied OpenSSL or OpenSSL version 1.1.1. Please note: MongoDB doesn't offer a STARTTLS connection, IRC currently only works with --ssl-native
. irc
is WIP.