522 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			522 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env bash
 | 
						|
 | 
						|
set -o pipefail
 | 
						|
 | 
						|
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} ))
 | 
						|
		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(){
 | 
						|
	case "$1" in
 | 
						|
		0) echo -en "$GREEN" ;;
 | 
						|
		1) echo -en "$YELLOW" ;;
 | 
						|
		2) echo -en "$RED" ;;
 | 
						|
	esac
 | 
						|
}
 | 
						|
 | 
						|
printKey(){
 | 
						|
	echo -e "Not checked\
 | 
						|
\t${GREEN}Fine${NC}\
 | 
						|
\t${YELLOW}Mis-configured${NC}\
 | 
						|
\t${RED}Missing${NC}"
 | 
						|
}
 | 
						|
 | 
						|
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 | grep -oE '[0-9]+' )"
 | 
						|
	if [ "$value" = "1" ]; then
 | 
						|
		return 0
 | 
						|
	else
 | 
						|
		echo "X-XSS-Protection" | drawInBox
 | 
						|
		wecho -e "The X-XSS-Protection header asks browsers to try and prevent \
 | 
						|
reflected cross site scripting attacks. It has been replaced in modern browsers \
 | 
						|
by the content-security-policy although should still be included for the sake \
 | 
						|
of old browsers\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(){
 | 
						|
#}
 | 
						|
 | 
						|
test_content-security-policy(){
 | 
						|
	local value
 | 
						|
	value="$(echo "$1" | cut -d ':' -f 2- | trimWhitespace)"
 | 
						|
	# TODO: work on content security testing
 | 
						|
	local message=""
 | 
						|
	
 | 
						|
	if [ -z "$value" ]; then
 | 
						|
		echo "Content-Security-Policy" | drawInBox
 | 
						|
		wecho -e "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"
 | 
						|
		return 2
 | 
						|
	else
 | 
						|
		if echo "$value" | grep -q 'unsafe-inline'; then
 | 
						|
			message+="The content security policy includes the \
 | 
						|
	${ORANGE}unsafe-inline${NC} property which allows for inline JS/CSS assets. \
 | 
						|
	This prevents the content security policy from effectively mitigating against
 | 
						|
	reflected or stored XSS attacks\n\n"
 | 
						|
		elif echo "$value" | grep -q 'unsafe-eval'; then
 | 
						|
			message+="The content security policy includes the \
 | 
						|
	${ORANGE}unsafe-eval${NC} property which allows for eval to be used in JS. \
 | 
						|
	This prevents the content security policy from effectively mitigating against
 | 
						|
	DOM based XSS attacks\n\n"
 | 
						|
		fi
 | 
						|
	fi
 | 
						|
	if [ -n "$message" ]; then
 | 
						|
		echo "Content-Security-Policy" | drawInBox
 | 
						|
		message="$(echo "$message" | tr -d '\t')"
 | 
						|
		wecho -e "$message"
 | 
						|
		return 1
 | 
						|
	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=""
 | 
						|
	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 -q '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
 | 
						|
 | 
						|
	#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 -q "HttpOnly"; then
 | 
						|
		echo "$value"
 | 
						|
		echo "$value" | grep -qi "HttpOnly" --color always
 | 
						|
		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 -q "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 [ "$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
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
usage(){
 | 
						|
	echo -n "analyse-headers [OPTIONS]... URL
 | 
						|
 | 
						|
	Analyse the headers of a website
 | 
						|
 | 
						|
	Options:
 | 
						|
	-h, --help              Display this help and exit
 | 
						|
"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# 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
 | 
						|
 | 
						|
 | 
						|
# Read the options and set stuff
 | 
						|
while [[ $1 = -?* ]]; do
 | 
						|
	case $1 in
 | 
						|
		-h|--help) usage; exit;;
 | 
						|
		--) 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 "$url")"
 | 
						|
fi
 | 
						|
 | 
						|
missingHeaders="x-frame-options
 | 
						|
content-security-policy
 | 
						|
x-xss-protection
 | 
						|
x-content-type-options
 | 
						|
feature-policy
 | 
						|
permissions-policy
 | 
						|
expect-ct"
 | 
						|
 | 
						|
tmpfile="$(mktemp)"
 | 
						|
touch "$tmpfile"
 | 
						|
 | 
						|
printKey
 | 
						|
 | 
						|
echo ""
 | 
						|
 | 
						|
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
 | 
						|
		echo "$line"
 | 
						|
	fi
 | 
						|
done<<<"$(echo "$headers" | sed '1d')" # We don't want the initial http banner
 | 
						|
 | 
						|
echo "$missingHeaders" | while read -r line; do
 | 
						|
	echo -e "${RED}$line${NC}"
 | 
						|
	functionName="test_$line"
 | 
						|
	"$functionName" >> "$tmpfile"
 | 
						|
done
 | 
						|
 | 
						|
echo ""
 | 
						|
 | 
						|
cat "$tmpfile"
 | 
						|
rm "$tmpfile"
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 |