Skip to content

Commit a55fc6d

Browse files
authored
Merge pull request #3071 from testssl/potato-20-revive-hsts-preload
Potato 20 revive hsts preload
2 parents 2a30ddc + bfdfaf4 commit a55fc6d

7 files changed

Lines changed: 234 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55

66
* QUIC protocol check
77
* TLS 1.3 early data (0-RTT)
8-
* Support for RFC 8998 and draft-yang-tls-hybrid-sm2-mlkem (TLS_SM4_GCM_SM3, TLS_SM4_CCM_SM3 ciphers, kx groups curveSM2, curveSM2MLKEM768; SM2 pub keys + signatures)
8+
* Support for RFC 8998, draft-yang-tls-hybrid-sm2-mlkem (TLS_SM4_GCM_SM3, TLS_SM4_CCM_SM3 ciphers, kx groups curveSM2, curveSM2MLKEM768; SM2 pub keys + signatures)
99
* Adds a check for mandatory extended master secret TLS extension
1010
* Bump SSLlabs rating guide to 2009r
1111
* Check for Opossum vulnerability
12+
* `--phone-out` checks the HSTS preload list on https://hstspreload.org/
1213
* Enable IPv6 automagically, i.e. if target via IPv6 is reachable just (also) scan it
1314
* Provide an FAQ
1415

CREDITS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Full contribution, see git log.
1111
- extended parsing of TLS ServerHello messages
1212
- TLS 1.3 support (final and pre-final) with needed en/decryption
1313
- add several TLS extensions
14-
- Several ciphers and curves added
14+
- Several ciphers and curves added
1515
- Detection + output of multiple certificates
1616
- several cleanups of server certificate related stuff
1717
- testssl.sh -e/-E: testing with a mixture of openssl + sockets
@@ -55,6 +55,7 @@ Full contribution, see git log.
5555
- maximum certificate lifespan of 398 days
5656
- ssl renegotiation amount variable
5757
- custom http request headers
58+
- HSTS preload list lookup (finalized: Mayank)
5859

5960
* Frank Breedijk
6061
- Detection of insecure redirects

doc/testssl.1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,8 @@ can try to apply evasion techniques by changing the variables USLEEP_SND
417417
and / or USLEEP_REC and maybe MAX_WAITSOCK.
418418
.PP
419419
\f[CR]\-\-phone\-out\f[R] Checking for revoked certificates via CRL and
420-
OCSP is not done per default.
420+
OCSP, as well as the HSTS preload list status via hstspreload.org, is
421+
not done per default.
421422
This switch instructs testssl.sh to query external \(en in a sense of
422423
the current run \(en URIs.
423424
By using this switch you acknowledge that the check might have privacy
@@ -603,6 +604,10 @@ by detection or by enforcing via \f[CR]\-\-assume\-http\f[R].
603604
It tests several HTTP headers like
604605
.IP \(bu 2
605606
HTTP Strict Transport Security (HSTS)
607+
.RS 2
608+
.IP \(bu 2
609+
HSTS preload list status (when \f[CR]\-\-phone\-out\f[R] supplied)
610+
.RE
606611
.IP \(bu 2
607612
HTTP Public Key Pinning (HPKP)
608613
.IP \(bu 2

doc/testssl.1.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,8 @@ <h3 id="tuning-options">TUNING OPTIONS</h3>
396396
evasion techniques by changing the variables USLEEP_SND and / or
397397
USLEEP_REC and maybe MAX_WAITSOCK.</p>
398398
<p><code>--phone-out</code> Checking for revoked certificates
399-
via CRL and OCSP is not done per default. This switch instructs
399+
via CRL and OCSP, as well as the HSTS preload list status via
400+
hstspreload.org, is not done per default. This switch instructs
400401
testssl.sh to query external – in a sense of the current run –
401402
URIs. By using this switch you acknowledge that the check might
402403
have privacy issues, a download of several megabytes (CRL file)
@@ -548,7 +549,11 @@ <h3 id="single-check-options">SINGLE CHECK OPTIONS</h3>
548549
<code>--assume-http</code>. It tests several HTTP headers
549550
like</p>
550551
<ul>
551-
<li>HTTP Strict Transport Security (HSTS)</li>
552+
<li>HTTP Strict Transport Security (HSTS)
553+
<ul>
554+
<li>HSTS preload list status (when <code>--phone-out</code>
555+
supplied)</li>
556+
</ul></li>
552557
<li>HTTP Public Key Pinning (HPKP)</li>
553558
<li>Server banner</li>
554559
<li>HTTP date+time</li>

doc/testssl.1.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ The same can be achieved by setting the environment variable `WARNINGS`.
152152

153153
`--ids-friendly` is a switch which may help to get a scan finished which otherwise would be blocked by a server side IDS. This switch skips tests for the following vulnerabilities: Heartbleed, CCS Injection, Ticketbleed and ROBOT. The environment variable OFFENSIVE set to false will achieve the same result. Please be advised that as an alternative or as a general approach you can try to apply evasion techniques by changing the variables USLEEP_SND and / or USLEEP_REC and maybe MAX_WAITSOCK.
154154

155-
`--phone-out` Checking for revoked certificates via CRL and OCSP is not done per default. This switch instructs testssl.sh to query external -- in a sense of the current run -- URIs. By using this switch you acknowledge that the check might have privacy issues, a download of several megabytes (CRL file) may happen and there may be network connectivity problems while contacting the endpoint which testssl.sh doesn't handle. PHONE_OUT is the environment variable for this which needs to be set to true if you want this.
155+
`--phone-out` Checking for revoked certificates via CRL and OCSP, as well as the HSTS preload list status via hstspreload.org, is not done per default. This switch instructs testssl.sh to query external -- in a sense of the current run -- URIs. By using this switch you acknowledge that the check might have privacy issues, a download of several megabytes (CRL file) may happen and there may be network connectivity problems while contacting the endpoint which testssl.sh doesn't handle. PHONE_OUT is the environment variable for this which needs to be set to true if you want this.
156156

157157
`--add-ca <CAfile>` enables you to add your own CA(s) in PEM format for trust chain checks. `CAfile` can be a directory containing files with a \.pem extension, a single file or multiple files as a comma separated list of root CAs. Internally they will be added during runtime to all CA stores. This is (only) useful for internal hosts whose certificates are issued by internal CAs. Alternatively ADDTL_CA_FILES is the environment variable for this.
158158

@@ -213,6 +213,7 @@ Also for multiple server certificates are being checked for as well as for the c
213213
`-h, --header, --headers` if the service is HTTP (either by detection or by enforcing via `--assume-http`. It tests several HTTP headers like
214214

215215
* HTTP Strict Transport Security (HSTS)
216+
- HSTS preload list status (when `--phone-out` supplied)
216217
* HTTP Public Key Pinning (HPKP)
217218
* Server banner
218219
* HTTP date+time

t/53_hsts_preload.t

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env perl
2+
3+
# Check the HSTS preload list status against the hstspreload.org API (needs --phone-out).
4+
# github.com is on the preload list, example.com is not.
5+
#
6+
# We don't use a full run, only the HTTP header section.
7+
8+
use strict;
9+
use Test::More;
10+
11+
my $tests = 0;
12+
my $prg="./testssl.sh";
13+
my $csv="tmp.csv";
14+
my $cat_csv="";
15+
my $check2run="-q --color 0 --phone-out --ip=one --headers --csvfile $csv";
16+
my $uri="github.com";
17+
my @args="";
18+
19+
die "Unable to open $prg" unless -f $prg;
20+
21+
# Provide proper start conditions
22+
unlink $csv;
23+
24+
#1 run -- a domain which is on the HSTS preload list
25+
printf "\n%s\n", "Unit test for HSTS preload list status against \"$uri\"";
26+
@args="$prg $check2run $uri >/dev/null";
27+
system("@args") == 0
28+
or die ("FAILED: \"@args\" ");
29+
$cat_csv=`cat $csv`;
30+
31+
# github.com is on the preload list
32+
like($cat_csv, qr/"HSTS_preloadAPI".*"preloaded"/,"\"$uri\" should be on the HSTS preload list");
33+
$tests++;
34+
unlink $csv;
35+
36+
#2 run -- a domain which is NOT on the HSTS preload list
37+
$uri="example.com";
38+
@args="$prg $check2run $uri >/dev/null";
39+
system("@args") == 0
40+
or die ("FAILED: \"@args\" ");
41+
$cat_csv=`cat $csv`;
42+
43+
# example.com is not on the preload list
44+
like($cat_csv, qr/"HSTS_preloadAPI".*"no entry"/,"\"$uri\" should not be on the HSTS preload list");
45+
$tests++;
46+
unlink $csv;
47+
48+
done_testing($tests);
49+
printf "\n";
50+
51+
52+
# vim:ts=5:sw=5:expandtab

testssl.sh

Lines changed: 163 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,6 +2262,76 @@ check_revocation_ocsp() {
22622262
fi
22632263
}
22642264

2265+
# Checks a domain against the hstspreload.org HSTS preload list API (requires --phone-out).
2266+
# arg1: domain to check
2267+
# arg2: JSON key to check (e.g. status, bulk, preloadedDomain). Empty: only (re)fetch the response.
2268+
# arg3: value the key is expected to have (without surrounding quotes; quoting is handled here)
2269+
# Return values:
2270+
# 0 - request made, nothing compared (no key supplied)
2271+
# 1 - API request failed (connection error)
2272+
# 10 - key matched the expected value
2273+
# 20 - key present but value did not match
2274+
# 21 - key not found in the response
2275+
#
2276+
check_hsts_preloadlist_match() {
2277+
local domain="$1"
2278+
local key="$2"
2279+
local value="$3"
2280+
local response=""
2281+
local tmpfile="$TEMPDIR/$NODE.hsts-preloadlist.txt"
2282+
local uri_api_status="https://hstspreload.org/api/v2/status?domain=$domain"
2283+
2284+
"$PHONE_OUT" || return 0
2285+
2286+
# Only query the API once per host, then reuse the cached response
2287+
if [[ ! -f "$tmpfile" ]]; then
2288+
http_get "$uri_api_status" "$tmpfile" || return 1
2289+
fi
2290+
response="$(<"$tmpfile")"
2291+
2292+
# Without a key we only (re)fetched the response
2293+
[[ -z "$key" ]] && return 0
2294+
2295+
# The key must be present, otherwise the API may have changed
2296+
[[ "$response" == *"\"$key\""* ]] || { debugme echo "HSTS preloadlist key unrecognized: $key"; return 21; }
2297+
2298+
# String values are quoted in the JSON, booleans are not, so accept either form
2299+
[[ "$response" == *"\"$key\": \"$value\""* || "$response" == *"\"$key\": $value"* ]] && return 10
2300+
return 20
2301+
}
2302+
2303+
# Returns the value of a known key from the hstspreload.org preload list API.
2304+
# Depends on check_hsts_preloadlist_match().
2305+
# arg1: domain to check
2306+
# arg2: key to resolve (status or bulk)
2307+
# Echoes the matched value and returns 0, or returns 1 if no known value matched.
2308+
#
2309+
check_hsts_preloadlist_value() {
2310+
local domain="$1"
2311+
local key="$2"
2312+
local -a values=()
2313+
local value
2314+
local value_ret=""
2315+
2316+
[[ -z "$key" ]] && return 1
2317+
2318+
# Only test against known values instead of echoing the API response back,
2319+
# so no untrusted input is reflected.
2320+
case "$key" in
2321+
status) values=("unknown" "pending" "rejected" "preloaded") ;;
2322+
bulk) values=("true" "false") ;;
2323+
*) return 1 ;;
2324+
esac
2325+
2326+
for value in "${values[@]}"; do
2327+
check_hsts_preloadlist_match "$domain" "$key" "$value"
2328+
[[ $? -eq 10 ]] && value_ret="$value" && break
2329+
done
2330+
2331+
[[ -n "$value_ret" ]] && safe_echo "$value_ret" && return 0
2332+
return 1
2333+
}
2334+
22652335
# waits maxsleep 1/10 seconds (arg2) until process with arg1 (pid) will be killed
22662336
#
22672337
# return values
@@ -2918,6 +2988,8 @@ run_hsts() {
29182988
local hsts_age_days
29192989
local spaces=" "
29202990
local jsonID="HSTS"
2991+
local json_postfix=""
2992+
local preloadmarked preloadsame preloadbulk preloadcombined=""
29212993

29222994
if [[ ! -s $HEADERFILE ]]; then
29232995
run_http_header "$1" || return 1
@@ -2971,18 +3043,105 @@ run_hsts() {
29713043
fi
29723044
if preload "$TMPFILE"; then
29733045
fileout "${jsonID}_preload" "OK" "domain IS marked for preloading"
3046+
preloadmarked=true
29743047
else
29753048
fileout "${jsonID}_preload" "INFO" "domain is NOT marked for preloading"
2976-
#FIXME: To be checked against preloading lists,
2977-
# e.g. https://dxr.mozilla.org/mozilla-central/source/security/manager/boot/src/nsSTSPreloadList.inc
2978-
# https://chromium.googlesource.com/chromium/src/+/master/net/http/transport_security_state_static.json
3049+
preloadmarked=false
29793050
fi
29803051
else
29813052
pr_svrty_low "not offered"
29823053
fileout "$jsonID" "LOW" "not offered"
3054+
preloadmarked=false
29833055
fi
29843056
outln
29853057

3058+
# Check the domain against the hstspreload.org HSTS preload list (requires --phone-out).
3059+
# Run this regardless of the served header: a domain may still be listed after the header
3060+
# was removed, or be rejected because the served header does not meet the requirements.
3061+
if "$PHONE_OUT"; then
3062+
json_postfix="_preloadAPI"
3063+
pr_bold " HSTS preload API "
3064+
3065+
# If the domain itself is the preloaded entry, it may be fine that the header omits 'preload'
3066+
check_hsts_preloadlist_match "$NODE" "preloadedDomain" "$NODE"
3067+
[[ $? -eq 10 ]] && preloadsame=true || preloadsame=false
3068+
3069+
# bulk=true: added via the submission form; false: manual addition or a subdomain
3070+
check_hsts_preloadlist_match "$NODE" "bulk" "true"
3071+
[[ $? -eq 10 ]] && preloadbulk=true || preloadbulk=false
3072+
3073+
# Combine the three booleans for a compact lookup, e.g. marked+same+bulk -> "111"
3074+
[[ $preloadmarked == true ]] && preloadcombined="${preloadcombined}1" || preloadcombined="${preloadcombined}0"
3075+
[[ $preloadsame == true ]] && preloadcombined="${preloadcombined}1" || preloadcombined="${preloadcombined}0"
3076+
[[ $preloadbulk == true ]] && preloadcombined="${preloadcombined}1" || preloadcombined="${preloadcombined}0"
3077+
debugme echo "Temporary lookupvariable: $preloadcombined"
3078+
3079+
# Determine and show the outcome
3080+
case "$(check_hsts_preloadlist_value "$NODE" "status")" in
3081+
"unknown") # Not found in the HSTS preload list
3082+
case "$preloadcombined" in
3083+
"000"|"001"|"010"|"011")
3084+
outln "no entry"
3085+
fileout "${jsonID}${json_postfix}" "INFO" "no entry"
3086+
;;
3087+
"100"|"101"|"110"|"111")
3088+
pr_svrty_low "no entry"
3089+
outln " -- submit to HSTS preload list"; fileout "${jsonID}${json_postfix}" "LOW" "no entry"
3090+
;;
3091+
esac
3092+
;;
3093+
"pending") # Currently in the HSTS pending list
3094+
case "$preloadcombined" in
3095+
"000"|"001"|"010"|"100"|"101"|"110"|"111")
3096+
outln "pending"
3097+
fileout "${jsonID}${json_postfix}" "INFO" "pending"
3098+
;;
3099+
"011") pr_svrty_medium "pending"
3100+
outln " -- addition going to fail, add header"
3101+
fileout "${jsonID}${json_postfix}" "MEDIUM" "pending"
3102+
;;
3103+
esac
3104+
;;
3105+
"rejected") # Entry is considered rejected by the HSTS list
3106+
case "$preloadcombined" in
3107+
"000"|"001"|"010"|"011")
3108+
outln "rejected"
3109+
fileout "${jsonID}${json_postfix}" "INFO" "rejected"
3110+
;;
3111+
"100"|"101"|"110"|"111")
3112+
pr_svrty_medium "rejected" ; outln " -- check other requirements"
3113+
fileout "${jsonID}${json_postfix}" "MEDIUM" "rejected"
3114+
;;
3115+
esac
3116+
;;
3117+
"preloaded") # Marked as 'preload' in the HSTS preload list
3118+
case "$preloadcombined" in
3119+
"000"|"001")
3120+
prln_svrty_good "preloaded"
3121+
fileout "${jsonID}${json_postfix}" "OK" "preloaded"
3122+
;;
3123+
"010")
3124+
outln "preloaded -- manual addition detected"
3125+
fileout "${jsonID}${json_postfix}" "INFO" "preloaded"
3126+
;;
3127+
"011")
3128+
pr_svrty_medium "preloaded"
3129+
outln " -- list may remove entry, add header"
3130+
fileout "${jsonID}${json_postfix}" "MEDIUM" "preloaded"
3131+
;;
3132+
"100"|"101"|"110"|"111")
3133+
prln_svrty_best "preloaded"
3134+
fileout "${jsonID}${json_postfix}" "OK" "preloaded"
3135+
;;
3136+
esac
3137+
;;
3138+
*) # Empty: the hstspreload.org API was unreachable or returned an unexpected response
3139+
prln_warning "not checked (HSTS preload list lookup failed)"
3140+
fileout "${jsonID}${json_postfix}" "WARN" "HSTS preload list could not be checked"
3141+
;;
3142+
esac
3143+
fi
3144+
29863145
tmpfile_handle ${FUNCNAME[0]}.txt
29873146
return 0
29883147
}
@@ -21708,7 +21867,7 @@ tuning / connect options (most also can be preset via environment variables):
2170821867
--sneaky leave less traces in target logs: user agent, referer
2170921868
--user-agent <user agent> set a custom user agent instead of the standard user agent
2171021869
--ids-friendly skips a few vulnerability checks which may cause IDSs to block the scanning IP
21711-
--phone-out allow to contact external servers for CRL download and querying OCSP responder
21870+
--phone-out allow to contact external servers for CRL download, querying OCSP responder and the HSTS preload API
2171221871
--add-ca <CA files|CA dir> path to <CAdir> with *.pem or a comma separated list of CA files to include in trust check
2171321872
--mtls <CLIENT CERT file> path to <CLIENT CERT> file in PEM format containing unencrypted certificate key (beta)
2171421873
--basicauth <user:pass> provide HTTP basic auth information

0 commit comments

Comments
 (0)