HSTS and http security headers

As I endeavor to learn more about (web)security, I decided to harden this site to serve content only via TLS as well as to include it in the HSTS preload list. By submitting it for inclusion in the list, the domain name bghost.xyz will be hard-coded into Chromium and its proprietary descendant Google Chrome as well as Mozilla Firefox. This is an implementation of IETF rfc6797, HTTP Strict Transport Security (HSTS). Once included in the list, attempts to visit http://bghost.xyz will be redirected and upgraded to https://bghost.xyz in the browser, making it impossible to access the site over a non-secure connection using any of the browsers which support HSTS preloading, as well as helping to prevent man-in-the-middle (MitM) attacks.

The certificate for this site is provided by Let’s Encrypt using the certbot tool from the Electronic Frontier Foundation. Without Let’s Encrypt, it would not be feasible for small sites like this one to deploy encrypted connections without incurring great expense, so consider a donation if you have the extra scratch.

Before the site can be submitted for inclusion into the HSTS preload list we need to explicitly request Strict-Transport-Security, include the subdomains, and request preloading in the header. The max-age in this case is two years in seconds, which is a current best practice. It is NOT, however, a good idea to set the max-age to a number this high until the site has been up and running with all these things enabled and is still accessible. If you screw this up and set too high of a max-age it is possible to deny access to the site.

Here is the relevant part of the .htaccess file:

# HSTS redirect to HTTPS only and set for 2 year duration in seconds;
# include preload header to serve site as HTTPS only
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" \
    "expr=%{req_novary:X-Forwarded-Proto}=='https'"

Another important part of hardening a website is to define a Content Security Policy. This is used to define where various web resources can be loaded from and can also be used to mitigate cross-site scripting (XSS) attacks. Below is the Content Security Policy for this site:

# explicitly stipulate what content can be loaded and from where
# "require-sri-for" allows only scripts that match hash to be loaded
# "unsafe-inline" is bad to use, but is necessary for this **this** code block to render
Header always set Content-Security-Policy "default-src 'none'"; \
    "font-src 'self' https://fonts.gstatic.com"; \
    "img-src https: 'self'; object-src 'none'; script-src 'self'"; \
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com"; \
    "require-sri-for script; frame-ancestors 'none'"

Overall, the above Content Security Policy (CSP) uses secure practices such as limiting what can be loaded and where it can be loaded from to this domain and a couple of specific Google Fonts, the main weakness is that it allows for ‘unsafe-inline’ in the style-src directive, which is necessary because of the way that code blocks are generated as inline styles by HUGO in the compilation of this website. However, the CSP does require that all javascript originates from this domain, as well as invoking the upcoming standard of requiring Subresource Integrity for scripts, which once supported by various browsers, will mean that browsers will refuse to load scripts without a valid integrity attibute (a cryptographic hash of the script’s content) throughout this site. Here is an example piece of code from this site’s source, which lazily loads some webfonts from Google:

  </footer>
<script src="https://bghost.xyz/js/loadFont.js"
  crossorigin="anonymous"
  integrity="sha384-ozIb1XP1Ev3EYomx00KXInfa18bPQRCywaaDlTGoRLR3c8ViQqtCbzqwstXcp9Wr"
  type="text/javascript">
</script>
</body>
</html>

When the script is invoked with the above Content Security Policy in a supported environment (which is currently limited to using some development switches in Chrom{e,ium} and Firefox), the browser will check the content of the script against the value of the hash, in this case using sha384. If the hash of the script file is not equal to the hash in the integrity property, the browser will fail to load the script, therefore preventing a malicious script from being loaded in place of the intended one.

The following section is a clickjacking defense which prevents any domain from attempting to frame the content of the pages on this site. It is also possible to specify “SAMEORIGIN” in place of “DENY”, which would allow the current site to frame the content. This is being superseded by the frame-ancestors property in the Content Security Policy, but is a good backup for browsers that do not yet support that property.

# clickjacking defense (disallow framing of content)
Header always set X-Frame-Options "DENY"

The next section provides a defense against cross-site scripting (XSS). If the browser detects an attempt to load an XSS payload, it will not render the page.

# enable XSS (cross-site scripting) filtering and do not render the page if XSS attack is detected
Header always set X-Xss-Protection "1; mode=block"

The following header disallows MIME-sniffing, which was used (mostly) by Internet Explorer to detect various types of content so they could be correctly displayed. This is outdated and can open up security vulnerabilities.

# prevent MIME-sniffing
Header always set X-Content-Type-Options "nosniff"

The next code snippet is designed to protect user privacy. When a user leaves this site to load another one in the same browser window or tab, this page will not send a referral link so the next site will not know where they came from.

# don't send origin info when navigating to another site in the same tab / window
Header always set Referrer-Policy: "no-referrer"

With the above options, this site is on its way to being more private for its visitors as well as verifiably displaying the content that it is supposed to. While a static site such as this one, which does not allow for any user input, is already relatively secure, in an effort to learn some best practices and prepare for future changes, I have decided to spend the time to get this set up and documented. All of this info above is concatenated below. When we put it all together, the .htaccess file looks like this:

# .htaccess

# HSTS redirect to HTTPS only and set for 2 year duration in seconds;
# include preload header to serve site as HTTPS only
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" \
    "expr=%{req_novary:X-Forwarded-Proto}=='https'"
# explicitly stipulate what content can be loaded and from where
# "require-sri-for" allows only scripts that match hash to be loaded
# "unsafe-inline" is bad to use, but is necessary for this **this** code block to render
Header always set Content-Security-Policy "default-src 'none'"; \
    "font-src 'self' https://fonts.gstatic.com"; \
    "img-src https: 'self'; object-src 'none'; script-src 'self'"; \
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com"; \
    "require-sri-for script; frame-ancestors 'none'"
# clickjacking defense (disallow framing of content)
Header always set X-Frame-Options "DENY"
# enable XSS (cross-site scripting) filtering and do not render the page if XSS attack is detected
Header always set X-Xss-Protection "1; mode=block"
# prevent MIME-sniffing
Header always set X-Content-Type-Options "nosniff"
# don't send origin info when navigating to another site in the same tab / window
Header always set Referrer-Policy: "no-referrer"

# set custom error pages for 40{3,4} (not related to security)
ErrorDocument 404 /err404.html
ErrorDocument 403 /err403.html

I would like to acknowledge the work of Scott Helme for his various blogposts, as well as providing securityheaders.io which I used to test this site as well as the Mozilla Developer Network, and Observatory. Also, I was inspired go down this rabbit hole by Julia Evans who makes great zines about Linux debugging tools and other technical things.

If you appreciated this post or find any errors or ommissions, please contact me and I will do my best to fix or clarify things.