The changes amount to the following. x-xss-protection now "passes" if it's set to 1; mode=block The CSP now fails if it doesn't have either a script-src or a default-src. It now checks for referrer-policy simple mode is available which doesn't use colours, and instead prepends each line with either "Misconfigured", "Good", or "Missing. Useful for automating"
900 lines
26 KiB
Bash
Executable file
900 lines
26 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -o pipefail
|
|
|
|
lotsfile="${XDG_DATA_HOME:-$HOME/.local/share}/analyse-headers/lots-domains.json"
|
|
|
|
die(){
|
|
echo "$@" >&2
|
|
exit 1
|
|
}
|
|
|
|
#RED='\033[0;31m'
|
|
RED='\033[1;31m'
|
|
YELLOW='\033[1;33m'
|
|
GREEN='\033[1;32m'
|
|
LBLUE='\033[1;34m'
|
|
LCYAN='\033[1;36m'
|
|
ORANGE='\033[0;33m'
|
|
LGREY='\033[0;37m'
|
|
BOLDJ='\033[1;37m'
|
|
NC='\033[0m' # No Color
|
|
|
|
stripAnsi(){
|
|
sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"
|
|
}
|
|
|
|
trimWhitespace(){
|
|
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
|
|
}
|
|
|
|
#wrapped echo
|
|
wecho(){
|
|
builtin echo -e "$@" | fold -s -w 80
|
|
}
|
|
|
|
drawInBox(){
|
|
innerWidth="45"
|
|
echo -en "${LBLUE}╭"
|
|
head -c $innerWidth /dev/zero | tr '\0' '-'
|
|
echo -e "╮${NC}"
|
|
while IFS= read -r line; do
|
|
# The ansi characters mess up the string length so we need to strip them to calculate the width
|
|
stripped="$(echo -n "$line" | stripAnsi)"
|
|
leftPad=$(( ( innerWidth - ${#stripped} ) / 2))
|
|
rightPad=$(( ( innerWidth - leftPad ) - ${#stripped} ))
|
|
if [ "${#stripped}" -gt "$innerWidth" ]; then
|
|
line="$(echo -n "$line" | fold -w $((innerWidth - 5)) | head -n 1)..."
|
|
stripped="$(echo -n "$line" | stripAnsi)"
|
|
leftPad=$(( ( innerWidth - ${#stripped} ) / 2))
|
|
rightPad=$(( ( innerWidth - leftPad ) - ${#stripped} ))
|
|
fi
|
|
echo -en "${LBLUE}|${NC}"
|
|
head -c $leftPad /dev/zero | tr '\0' ' '
|
|
echo -n "$line"
|
|
head -c $rightPad /dev/zero | tr '\0' ' '
|
|
echo -e "${LBLUE}|${NC}"
|
|
done
|
|
echo -en "${LBLUE}╰"
|
|
head -c $innerWidth /dev/zero | tr '\0' '-'
|
|
echo -e "╯${NC}"
|
|
}
|
|
|
|
# gets the colour that should be output
|
|
# 0 = green
|
|
# 1 = yellow
|
|
# 2 = red
|
|
getColour(){
|
|
if [ "$simple" == "true" ]; then
|
|
case "$1" in
|
|
0) echo "Good - " ;;
|
|
1) echo "Misconfigured - " ;;
|
|
2) echo "Missing - " ;;
|
|
esac
|
|
|
|
else
|
|
case "$1" in
|
|
0) echo -en "$GREEN" ;;
|
|
1) echo -en "$YELLOW" ;;
|
|
2) echo -en "$RED" ;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
printKey(){
|
|
echo -e "Not checked\
|
|
\t${GREEN}Fine${NC}\
|
|
\t${YELLOW}Mis-configured${NC}\
|
|
\t${RED}Missing${NC}"
|
|
}
|
|
|
|
fetchLots(){
|
|
# Make sure the directory exists
|
|
mkdir -p "$(dirname "$lotsfile")"
|
|
# Make sure we have jq and pup
|
|
type -p jq >/dev/null 2>&1 || die "You need jq to update lots"
|
|
# Update lots
|
|
type -p pup >/dev/null 2>&1 || die "You need pup to update lots"
|
|
# Update lots
|
|
curl https://lots-project.com/ | pup 'table#main-table tr json{}' | jq -r \
|
|
'[ .[] | . |= {
|
|
Site: .children[0]?.children[]?.text?,
|
|
Tags: [ .children[1]?.children[]?.children[]?.text ]
|
|
} | objects] | map( { (.Site): [ .Tags[] ]} ) | add' > "$lotsfile"
|
|
}
|
|
|
|
generic_version_disclosure(){
|
|
local value
|
|
local header
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
header="$(echo "$1" | cut -d ':' -f 1 | trimWhitespace)"
|
|
echo "$header" | drawInBox
|
|
wecho -e "The server responds with ${ORANGE}$value${NC} in the \
|
|
$header header"
|
|
wecho -e "This is potentially un-necesary information disclosure\n\n"
|
|
[ -n "$value" ] && return 1 || return 0
|
|
}
|
|
|
|
test_server(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2 | trimWhitespace)"
|
|
echo "Server" | drawInBox
|
|
wecho -e "The server responds with ${ORANGE}$value${NC} in the Server header"
|
|
wecho -e "This is potentially un-necesary information disclosure\n\n"
|
|
[ -n "$value" ] && return 1 || return 0
|
|
}
|
|
|
|
test_x-powered-by(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2 | trimWhitespace)"
|
|
echo "X-Powered-By" | drawInBox
|
|
wecho -e "The server responds with ${ORANGE}$value${NC} in the X-Powered-By header"
|
|
wecho -e "This is potentially un-necesary information disclosure\n\n"
|
|
[ -n "$value" ] && return 1 || return 0
|
|
}
|
|
|
|
test_x-xss-protection(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2 | trimWhitespace )"
|
|
if [ "$value" = "0" ] || [ "$value" = "1; mode=block" ]; then
|
|
return 0
|
|
else
|
|
echo "X-XSS-Protection" | drawInBox
|
|
wecho -e "The X-XSS-Protection header used to ask browsers to try and use \
|
|
internal heuristics to prevent reflected XSS attacks. It has been depreciated in all \
|
|
modern browsers that used to implement it.
|
|
|
|
OWASP now suggests setting it to 0.
|
|
https://owasp.org/www-project-secure-headers/#x-xss-protection\n\n"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_x-frame-options(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2 | trimWhitespace | tr '[:lower:]' '[:upper:]')"
|
|
case "$value" in
|
|
"SAMEORIGIN"|"DENY") return 0 ;;
|
|
"ALLOW-FROM"*)
|
|
echo "X-Frame-Opitons" | drawInBox
|
|
wecho -e "The ALLOW-FROM derivative is obsolete and no longer works \
|
|
in modern browsers.\n\n"
|
|
wecho -e "The Content-Security-Policy HTTP header has a \
|
|
frame-ancestors directive which you can use instead.\n\n"
|
|
return 1
|
|
;;
|
|
*)
|
|
echo "X-Frame-Opitons" | drawInBox
|
|
wecho "The X-Frame-Options HTTP response header can be used to \
|
|
indicate whether or not a browser should be allowed to render a page in a \
|
|
<frame>, <iframe>, <embed> or <object>. Sites can use this to avoid \
|
|
click-jacking attacks, by ensuring that their content is not embedded into \
|
|
other sites."
|
|
|
|
if echo "$headers" |
|
|
grep -Eqi '^content-security-policy:.*frame-ancestors.*'; then
|
|
wecho "It looks like the content security policy contains the \
|
|
frame ancestors directive. This also mitigates against the clickjacking \
|
|
although browser support isn't as strong meaning you should still include the \
|
|
x-frame-options header"
|
|
fi
|
|
|
|
source="
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset='UTF-8' />
|
|
<meta name='viewport' content='width=device-width' />
|
|
<title>Clickjacking example</title>
|
|
<style type='text/css' media='screen'>
|
|
body{
|
|
width: 100vw;
|
|
height: 100vh;
|
|
border: 2px solid black;
|
|
}
|
|
iframe{
|
|
border: 3px solid black;
|
|
width: 80%;
|
|
height: 80%;
|
|
margin: 20px auto;
|
|
display: block;
|
|
}
|
|
h1, p{
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Clickjacking example</h1>
|
|
<iframe src='$url'>
|
|
</iframe>
|
|
<p>If content is rendered above, the site is vulnerable to clickjacking</p>
|
|
</body>
|
|
</html>
|
|
"
|
|
wecho "To verify, type paste the following into your browser:"
|
|
echo -e "\ndata:text/html;base64,$(echo "$source" | base64 -w 0)\n\n"
|
|
|
|
return 2
|
|
esac
|
|
}
|
|
|
|
test_x-content-type-options(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2- | tr 'A-Z' 'a-z' | trimWhitespace )"
|
|
if [ "$value" = "nosniff" ]; then
|
|
return 0
|
|
else
|
|
echo "X-Content-Type-Options" | drawInBox
|
|
wecho -e "The X-Content-Type-Options header stops a browser from \
|
|
trying to MIME-sniff the content type and forces it to stick with the \
|
|
declared content-type.\n\n"
|
|
[ -z "$1" ] && return 2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
lookup_lots(){
|
|
if [ -f "$lotsfile" ]; then
|
|
jq -r '.["'"$1"'"]?[]?' "$lotsfile" | tr '\n' ',' | sed 's/,/, /g'
|
|
else
|
|
echo "No Lots File"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
checkJsonp(){
|
|
# This list came from here: https://github.com/google/csp-evaluator/blob/a21f94e348b0dfb0245c65af522bf3137a9647de/allowlist_bypasses/jsonp.ts
|
|
local domains="9.chart.apis.google.com
|
|
a248.e.akamai.net
|
|
accounts.google.com
|
|
a.config.skype.com
|
|
afpeng.alimama.com
|
|
ajax.googleapis.com
|
|
an.yandex.ru
|
|
api.facebook.com
|
|
api.flickr.com
|
|
api.instagram.com
|
|
api.map.baidu.com
|
|
api.mixpanel.com
|
|
api.twitter.com
|
|
api.userlike.com
|
|
api.vk.com
|
|
appcenter.intuit.com
|
|
a.tiles.mapbox.com
|
|
autocomplete.travelpayouts.com
|
|
awaps.yandex.ru
|
|
bebezoo.1688.com
|
|
beta.gismeteo.ru
|
|
books.google.com
|
|
c1n2.hypercomments.com
|
|
c1n3.hypercomments.com
|
|
catalog.api.2gis.ru
|
|
cbks0.googleapis.com
|
|
ccrprod.alipay.com
|
|
cdn.jsdelivr.net
|
|
cdn.syndication.twimg.com
|
|
cdn.syndication.twitter.com
|
|
clients1.google.com
|
|
client.siteheart.com
|
|
community.adobe.com
|
|
connect.mail.ru
|
|
count.tbcdn.cn
|
|
cse.google.com
|
|
c.tiles.mapbox.com
|
|
d1f69o4buvlrj5.cloudfront.net
|
|
data.gongchang.com
|
|
de.blog.newrelic.com
|
|
detector.alicdn.com
|
|
dev.virtualearth.net
|
|
fast.wistia.com
|
|
fellowes.ugc.bazaarvoice.com
|
|
gdata.youtube.com
|
|
googleads.g.doubleclick.net
|
|
google.ru
|
|
googletagmanager.com
|
|
graph.facebook.com
|
|
group.aliexpress.com
|
|
gum.criteo.com
|
|
gupiao.baidu.com
|
|
h.cackle.me
|
|
ib.adnxs.com
|
|
i.cackle.me
|
|
id.rambler.ru
|
|
kecngantang.blogspot.com
|
|
links.services.disqus.com
|
|
m.addthis.com
|
|
maps.beeline.ru
|
|
maps.googleapis.com
|
|
maps.google.com
|
|
maps.google.de
|
|
maps.google.lv
|
|
maps.google.ru
|
|
mc.yandex.ru
|
|
mt1.googleapis.com
|
|
mts0.googleapis.com
|
|
mts1.googleapis.com
|
|
nominatim.openstreetmap.org
|
|
offer.alibaba.com
|
|
ok.go.mail.ru
|
|
pagead2.googlesyndication.com
|
|
partner.googleadservices.com
|
|
passport.ngs.ru
|
|
pass.yandex.com
|
|
pass.yandex.ru
|
|
pass.yandex.ua
|
|
pin.aliyun.com
|
|
pipes.yahooapis.com
|
|
plugins.mozilla.org
|
|
pro.netrox.sc
|
|
pubsub.pubnub.com
|
|
query.yahooapis.com
|
|
rec.ydf.yandex.ru
|
|
relap.io
|
|
rexchange.begun.ru
|
|
securepubads.g.doubleclick.net
|
|
se.wikipedia.org
|
|
share.yandex.net
|
|
ssl.google-analytics.com
|
|
suggest.taobao.com
|
|
syndication.twitter.com
|
|
target.ukr.net
|
|
tj.gongchang.com
|
|
translate.googleapis.com
|
|
translate.google.com
|
|
translate.yandex.net
|
|
tr.indeed.com
|
|
ulogin.ru
|
|
video.media.yql.yahoo.com
|
|
vimeo.com
|
|
wb.amap.com
|
|
widget.admitad.com
|
|
widgets.pinterest.com
|
|
wpd.b.qq.com
|
|
wslocker.ru
|
|
www.blogger.com
|
|
www.facebook.com
|
|
www.googleadservices.com
|
|
www.google-analytics.com
|
|
www.googleapis.com
|
|
www.google.com
|
|
www.google.de
|
|
www.googletagmanager.com
|
|
www.linkedin.com
|
|
www.meteoprog.ua
|
|
www-onepick-opensocial.googleusercontent.com
|
|
www.panoramio.com
|
|
www.sharethis.com
|
|
www.travelpayouts.com
|
|
www.youku.com
|
|
www.youtube.com
|
|
yandex.ru
|
|
ynuf.alipay.com"
|
|
if echo "$domains" | grep -Eq "$1"; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_content-security-policy(){
|
|
local value
|
|
local ret=0
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
# TODO: work on content security testing
|
|
local message=""
|
|
|
|
if [ -z "$value" ]; then
|
|
message+="The HTTP Content-Security-Policy response header allows web site \
|
|
administrators to control resources the user agent is allowed to load for a \
|
|
given page. With a few exceptions, policies mostly involve specifying server \
|
|
origins and script endpoints. This helps guard against cross-site scripting \
|
|
attacks (XSS).\n\n"
|
|
ret=2
|
|
else
|
|
# If we get here, the csp is set
|
|
|
|
local reportURI=false
|
|
local reportTO=false
|
|
local scriptOrDefaultSrc=false
|
|
|
|
[ -f "$lotsfile" ] || message+="WARNING: Lots file not available. Run with --fetch-lots in order to get it\n\n"
|
|
|
|
while read directive; do
|
|
local directiveName="$(echo "$directive" | cut -d ' ' -f 1)"
|
|
local directiveValue="$(echo "$directive" | cut -d ' ' -f 2-)"
|
|
case "$directiveName" in
|
|
"report-uri" ) reportURI=true ;;
|
|
"report-to" ) reportTO=true ;;
|
|
*"-src")
|
|
# check sources
|
|
if [ "$directiveName" = "script-src" ] | [ "$directiveName" = "default-src" ]; then
|
|
scriptOrDefaultSrc=true;
|
|
fi
|
|
while read source; do
|
|
sourcemessage=''
|
|
case "$source" in
|
|
"'self'")
|
|
sourcemessage+="This is normally fine \
|
|
although care should be taken if the site being tested has upload functionality"
|
|
ret=$((ret>1 ? ret : 1))
|
|
;;
|
|
"'unsafe-inline'")
|
|
sourcemessage+="This allows for inline assets and prevents the content \
|
|
security policy from effectively mitigating against reflected or stored XSS \
|
|
attacks"
|
|
;;
|
|
"'unsafe-eval'")
|
|
sourcemessage+="This allows for eval to be used in JS and prevents \
|
|
the content security policy from effectively mitigating against DOM based XSS \
|
|
attacks"
|
|
;;
|
|
"http:"|"https:"|"data:")
|
|
sourcemessage+="This allows the browser to source from any $source \
|
|
url. It is likely that a malicious actor could control one."
|
|
;;
|
|
"'"*)
|
|
sourcemessage+="${RED}Unknown source. URLS shouldn't have quotes around them${NC}"
|
|
;;
|
|
*)
|
|
local domain="$(echo "$source" | sed -E 's/([^/]*:\/\/)?([^/]*).*/\2/')"
|
|
lotsTags="$(lookup_lots "$domain")"
|
|
if [ $? -eq 0 ] && [ -n "$lotsTags" ]; then
|
|
sourcemessage+="The LOTS project has marked ${ORANGE}${domain}${NC} with the tags: $lotsTags."
|
|
fi
|
|
if [ "$directiveName" == "script-src" ] && checkJsonp "$domain"; then
|
|
[ -n "$sourcemessage" ] && sourcemessage+="\n\n"
|
|
sourcemessage+="${ORANGE}${domain}${NC} is known to host jsonp endpoints which can sometimes be used to bypass the CSP"
|
|
fi
|
|
sourcemessage="$(echo -n "$sourcemessage")"
|
|
;;
|
|
esac
|
|
|
|
if [ -n "$sourcemessage" ]; then
|
|
message+="The directive \
|
|
${ORANGE}$directiveName${NC} has a value of ${ORANGE}${source}${NC}.\n${sourcemessage}\n\n"
|
|
fi
|
|
|
|
done < <(echo "$directiveValue" | tr ' ' '\n' | trimWhitespace)
|
|
|
|
;;
|
|
esac
|
|
|
|
done < <(echo "$value" | tr ';' '\n' | trimWhitespace)
|
|
|
|
if [ "$reportTO" == "false" ]; then
|
|
message+="The content security policy doesn't include the \
|
|
${ORANGE}report-to${NC} directive which is used to report CSP violations.\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
|
|
fi
|
|
if [ "$reportURI" == "false" ]; then
|
|
message+="The content security policy doesn't include the \
|
|
${ORANGE}report-uri${NC} directive which is used to report CSP violations. \
|
|
Eventually the report-to header will deprecate this directive, but it is not \
|
|
yet supported in most browsers so including both is recomended.\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
if [ "$scriptOrDefaultSrc" == "false" ]; then
|
|
message+="The content security policy doesn't include the \
|
|
${ORANGE}script-src${NC} or ${ORANGE}script-src${NC} directive which are used \
|
|
add allowed script sources. Without either, any scripts are allowed by default.\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
|
|
# elif echo "$value" | grep -q 'unsafe-eval'; then
|
|
# ret=$((ret>1 ? ret : 1))
|
|
# fi
|
|
fi
|
|
if [ -n "$message" ]; then
|
|
message+="The content security policy should be carefully considered \
|
|
before implementing as mis-configuring it can lead to site breakages. Scripts \
|
|
and stylesheets should be sourced from a carefully curated list of trusted \
|
|
domains that do now allow user uploaded content. Some CDNs should also be \
|
|
avoided if they host outdated versions of libraries that are known to be \
|
|
vulnerable or JSONP content, as both of these can lead to Cross Site Scripting \
|
|
(XSS). In order to prevent other types of XSS attack, unsafe-inline and \
|
|
unsafe-eval sources should be avoided in favour of putting scripts / styles in \
|
|
external resources or, if that is not possible, whitelisted inline scripts / \
|
|
styles using <hash-algorithm>-<hash> sources.
|
|
|
|
In order to prevent use of plugins such as flash and silverlight, use the \
|
|
{code}object-src 'none'{/code} directive.
|
|
|
|
In order to prevent framing, use the {code}frame-ancestors 'none'{/code} \
|
|
directive.
|
|
|
|
The recomended header for APIs is
|
|
|
|
{code}
|
|
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'
|
|
{/code}
|
|
|
|
Which disables loading of all sub-resources and stops the API response being
|
|
framed.
|
|
|
|
There is also a related content-security-policy-report-only header that will \
|
|
not enforce rules, but will report violations. This is useful for testing \
|
|
purposes
|
|
|
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n\n"
|
|
echo "Content-Security-Policy" | drawInBox
|
|
message="$(echo "$message" | tr -d '\t')"
|
|
wecho -e "$message"
|
|
return "$ret"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
test_strict-transport-security(){
|
|
local value
|
|
local ret
|
|
local output
|
|
local maxAge
|
|
value="$(echo "$1" | cut -d ':' -f 2 | trimWhitespace)"
|
|
ret=0
|
|
output=""
|
|
if [ -z "$value" ]; then
|
|
output+="The HTTP Strict Transport Security response header intructs \
|
|
browsers to only connect to it via an encrypted channel.\n\n"
|
|
ret=2
|
|
else
|
|
maxAge="$(echo "$value" | grep -oE 'max-age=[0-9]+' |
|
|
grep -oE '[0-9]+')"
|
|
|
|
if [ "$maxAge" -lt "31536000" ]; then
|
|
output+="The max-age is set to a low value of ${ORANGE}$maxAge${NC}.
|
|
We suggest setting it to at least 31536000.\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
|
|
if ! echo "$value" | grep -qi 'includeSubDomains'; then
|
|
output+="The ${ORANGE}includeSubdomains${NC} property was not found. \
|
|
When included browsers won't connect to subdomains unless over an encrypted \
|
|
channel.\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
|
|
fi
|
|
#if ! echo "$value" | grep -q 'preload'; then
|
|
# output+="The preload property "
|
|
# ret=$((ret>1 ? ret : 1))
|
|
#fi
|
|
|
|
if [ "$ret" -gt 0 ]; then
|
|
echo "Strict-Transport-Security" | drawInBox
|
|
wecho -e "$output"
|
|
fi
|
|
return $ret
|
|
}
|
|
|
|
test_set-cookie(){
|
|
local value
|
|
local cookieName
|
|
local ret
|
|
local output
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
cookieName="$(echo "$value" | cut -d '=' -f 1)"
|
|
ret=0
|
|
output=""
|
|
|
|
if ! echo "$value" | grep -qi "HttpOnly"; then
|
|
output+="The HttpOnly flag isn't set which means the cookie value can \
|
|
be read by JavaScript. If a malicious actor manages to run JavaScript through \
|
|
methods like XSS, they may be able to steal the contents of cookies\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
|
|
if ! echo "$value" | grep -qi "Secure"; then
|
|
output+="The Secure flag isn't set which means the cookie could be \
|
|
sent over unencrypted channels\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
|
|
if ! echo "$value" | grep -qi "SameSite=Strict"; then
|
|
output+="The SameSite flag isn't set to Strict. The SameSite flag \
|
|
controls whether a cookie is sent with cross-origin requests, \
|
|
providing some protection against cross-site request forgery attacks.
|
|
Strict means the browser sends the cookie only for same-site requests\n\n"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
|
|
if echo "$value" | grep -iq "bigipserver"; then
|
|
local ip_enc="$(echo "$value" | cut -d '=' -f 2 | cut -d '.' -f 1)"
|
|
local port_enc="$(echo "$value" | cut -d '=' -f 2 | cut -d '.' -f 2)"
|
|
local ip="$(echo "ibase=10;obase=16;$ip_enc"| bc | grep -o .. | tac |
|
|
while read -r part; do echo -n "$((0x$part))."; done)"
|
|
local port="$((0x$(echo "ibase=10;obase=16;$port_enc" | bc | grep -o .. | tac | tr -d '\n') ))"
|
|
if echo "$ip" | grep -Eq '([0-9]{1,3}[\.]){3}[0-9]{1,3}'; then
|
|
output+="The Cookie discloses internal IP addresses used by the load ballencer\n"
|
|
output+="IP: $ip\n"
|
|
output+="Port: $port\n\n"
|
|
output+="Remediate this by enabling cookie encryption\n\
|
|
https://support.f5.com/csp/article/K7784?sr=14607726"
|
|
ret=$((ret>1 ? ret : 1))
|
|
fi
|
|
fi
|
|
|
|
|
|
if [ "$ret" -gt 0 ]; then
|
|
echo "Set-Cookie: $cookieName" | drawInBox
|
|
wecho -e "$output"
|
|
fi
|
|
return "$ret"
|
|
}
|
|
|
|
test_permissions-policy(){
|
|
if [ -z "$1" ]; then
|
|
echo "Permissions-Policy" | drawInBox
|
|
wecho "The Permission-Policy header replaces the Feature-Policy and is \
|
|
used to allow or disallow certain browser features or apis in the interest of \
|
|
security.\n\n"
|
|
return 2
|
|
fi
|
|
}
|
|
|
|
test_feature-policy(){
|
|
if [ -z "$1" ]; then
|
|
echo "Feature-Policy" | drawInBox
|
|
wecho "The Feature-Policy header was used to allow or disallow certian \
|
|
browser features or apis. It has been superceded by the permissions-policy
|
|
header but should still be included for legacy browsers.\n\n"
|
|
return 2
|
|
fi
|
|
if ! echo "$headers" | grep -Eqi '^permissions-policy'; then
|
|
echo "Feature-Policy" | drawInBox
|
|
wecho "The Feature-Policy header was used to allow or disallow certian \
|
|
browser features or apis. It has been superceded by the permissions-policy
|
|
header but should still be included for legacy browsers.
|
|
It has been highlighted because the Permissions-policy header wasn't found.\n\n"
|
|
return 2
|
|
fi
|
|
}
|
|
|
|
test_expect-ct(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
if [ -z "$1" ]; then
|
|
echo "Expect-CT" | drawInBox
|
|
wecho "When a site enables the Expect-CT header, they are requesting \
|
|
that the browser check that any certificate for that site appears in public \
|
|
CT logs.
|
|
Initially, set the header without the enforce option but with report in order \
|
|
to check for potential breakages.
|
|
The Expect-CT will likely become obsolete in June 2021. Since May 2018 new \
|
|
certificates are expected to support SCTs by default. Certificates before \
|
|
March 2018 were allowed to have a lifetime of 39 months, those will all be \
|
|
expired in June 2021.\n\n"
|
|
return 2
|
|
elif ! echo "$value" | grep -q "enforce"; then
|
|
echo "Expect-CT" | drawInBox
|
|
wecho "The enforce directive was not found. It can be useful to omit \
|
|
this whilst testing the header, but should be added once testing has finished.
|
|
Without the enforce directive, the browser will not refuse connections that \
|
|
violate the Certificate Transparency policy.
|
|
The Expect-CT will likely become obsolete in June 2021. Since May 2018 new \
|
|
certificates are expected to support SCTs by default. Certificates before \
|
|
March 2018 were allowed to have a lifetime of 39 months, those will all be \
|
|
expired in June 2021.\n\n"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_referer-policy-ct(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
if [ -z "$1" ]; then
|
|
echo "Referrer-Policy" | drawInBox
|
|
wecho "The Referrer-Policy HTTP header controls how much referrer \
|
|
information (sent via the Referer header) should be included with requests.\n\n"
|
|
return 2
|
|
elif ! echo "$value" | grep -q "enforce"; then
|
|
# TODO: add checks for different referer policy opitons
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_access-control-allow-origin(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
if [ "$value" = "*" ]; then
|
|
echo "Access-Control-Allow-Origin" | drawInBox
|
|
wecho "The Access-Control-Allow-Origin header indicates whether the \
|
|
response can be shared with requesting code from the given origin
|
|
The value was found to be * meaning any origin. This is not normally desirable.
|
|
\n"
|
|
return 1
|
|
elif echo "$value" | grep -q "null"; then
|
|
echo "Access-Control-Allow-Origin" | drawInBox
|
|
wecho "The Access-Control-Allow-Origin header indicates whether the \
|
|
response can be shared with requesting code from the given origin
|
|
The value was found to be null. the serialization of the Origin of any \
|
|
resource that uses a non-hierarchical scheme (such as data: or file: ) and \
|
|
sandboxed documents is defined to be \"null\". Many User Agents will grant \
|
|
such documents access to a response with an Access-Control-Allow-Origin: \
|
|
\"null\" header, and any origin can create a hostile document with a \"null\" \
|
|
Origin. The \"null\" value for the ACAO header should therefore be avoided.\n\n"
|
|
return 1
|
|
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
test_cache-control(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
if [ -z "$1" ] || ! echo "$value" | grep -q "no-store"; then
|
|
echo "Cache-Control" | drawInBox
|
|
wecho "The Cache-Control header instructs the browser if and for how \
|
|
long browsers may cache responses. If responses contain sensitive information, \
|
|
they should not be cached. In order to enforce this, add the no-store directive.\n"
|
|
echo -e "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control\n\n"
|
|
[ -z "$1" ] && return 2 || return 1
|
|
fi
|
|
|
|
}
|
|
|
|
test_referrer-policy(){
|
|
local value
|
|
value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
|
|
if [ -z "$1" ] || ! echo "$value" | grep -q "strict-origin"; then
|
|
echo "Referrer-policy" | drawInBox
|
|
wecho "This allows control over what information is sent to another site within the referrer header. The referrer header is commonly used in site analytics to understand where traffic to a site is coming from.\n"
|
|
echo -e "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control\n\n"
|
|
[ -z "$1" ] && return 2 || return 1
|
|
fi
|
|
|
|
}
|
|
|
|
usage(){
|
|
echo -n "analyse-headers [OPTIONS]... URL
|
|
|
|
Analyse the headers of a website
|
|
|
|
Options:
|
|
-h, --help Display this help and exit
|
|
-k, --insecure Ignores certificate errors
|
|
--fetch-lots Updates domains from https://lots-project.com/ and exit
|
|
--nojoke Prevents script making windows joke
|
|
"
|
|
}
|
|
|
|
|
|
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
|
|
# --foo bar
|
|
optstring=h
|
|
unset options
|
|
while (($#)); do
|
|
case $1 in
|
|
# If option is of type -ab
|
|
-[!-]?*)
|
|
# Loop over each character starting with the second
|
|
for ((i=1; i < ${#1}; i++)); do
|
|
c=${1:i:1}
|
|
|
|
# Add current char to options
|
|
options+=("-$c")
|
|
|
|
# If option takes a required argument, and it's not the last char make
|
|
# the rest of the string its argument
|
|
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
|
|
options+=("${1:i+1}")
|
|
break
|
|
fi
|
|
done
|
|
;;
|
|
|
|
# If option is of type --foo=bar
|
|
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
|
|
# add --endopts for --
|
|
--) options+=(--endopts) ;;
|
|
# Otherwise, nothing special
|
|
*) options+=("$1") ;;
|
|
esac
|
|
shift
|
|
done
|
|
set -- "${options[@]}"
|
|
unset options
|
|
|
|
insecure=""
|
|
simple="false"
|
|
|
|
windowsjoke=false
|
|
if grep -q Microsoft /proc/version; then
|
|
windowsjoke=true
|
|
fi
|
|
|
|
# Read the options and set stuff
|
|
while [[ $1 = -?* ]]; do
|
|
case $1 in
|
|
-h|--help) usage; exit;;
|
|
-k|--insecure) insecure="-k" ;;
|
|
--simple) simple="true" ;;
|
|
--fetch-lots ) fetchLots; exit ;;
|
|
--nojoke ) windowsjoke=false ;;
|
|
--) shift; break ;;
|
|
*) die "invalid option: '$1'." ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Store the remaining part as arguments.
|
|
args+=("$@")
|
|
|
|
url="${args[0]}"
|
|
[ -z "$url" ] && die "You need to specify a url"
|
|
|
|
# If url is -, read headers from stdin
|
|
if [ "$url" = "-" ]; then
|
|
headers="$(cat -)"
|
|
else
|
|
headers="$(curl -s -I $insecure "$url")"
|
|
fi
|
|
|
|
missingHeaders="x-frame-options
|
|
strict-transport-security
|
|
content-security-policy
|
|
x-xss-protection
|
|
x-content-type-options
|
|
permissions-policy
|
|
feature-policy
|
|
cache-control
|
|
referrer-policy"
|
|
|
|
tmpfile="$(mktemp)"
|
|
touch "$tmpfile"
|
|
|
|
if [ "$windowsjoke" == "true" ]; then
|
|
echo "Why would you use windows, do you hate yourself?"
|
|
fi
|
|
|
|
if [ "$simple" == "false" ]; then
|
|
printKey
|
|
echo ""
|
|
fi
|
|
|
|
|
|
echo "$headers" | sed -n '1p'
|
|
|
|
while read -r line; do
|
|
headerKey="$(echo "$line" | cut -d ':' -f1)"
|
|
lowercase="$(echo "$headerKey" | tr '[:upper:]' '[:lower:]')"
|
|
missingHeaders="$(echo -n "$missingHeaders" | sed '/^'"$lowercase"'$/d')"
|
|
functionName="test_$lowercase"
|
|
|
|
if declare -f "$functionName" > /dev/null; then
|
|
"$functionName" "$line" >> "$tmpfile"
|
|
colour="$(getColour "$?")"
|
|
echo -e "${colour}$line${NC}"
|
|
elif echo "$lowercase" | grep "version" > /dev/null; then
|
|
# if the word version is in the line, assume version disclosure
|
|
generic_version_disclosure "$line" >> "$tmpfile"
|
|
colour="$(getColour "$?")"
|
|
echo -e "${colour}$line${NC}"
|
|
else
|
|
if [ "$simple" == "false" ]; then
|
|
echo "$line"
|
|
fi
|
|
fi
|
|
done<<<"$(echo "$headers" | sed '1d')" # We don't want the initial http banner
|
|
|
|
echo "$missingHeaders" | while read -r line; do
|
|
if [ "$simple" == "false" ]; then
|
|
echo -e "${RED}$line${NC}"
|
|
else
|
|
echo "Missing - $line"
|
|
fi
|
|
functionName="test_$line"
|
|
"$functionName" >> "$tmpfile"
|
|
done
|
|
|
|
echo ""
|
|
|
|
if [ "$simple" == "false" ]; then
|
|
cat "$tmpfile"
|
|
fi
|
|
rm "$tmpfile"
|
|
|
|
|
|
|
|
|