5
\$\begingroup\$

Please see my previous post in the below hyperlink

I've updated my .htaccess file to account for an HSTS, along with many of the recommended changes. See the snippet below. I'd like to stress that implementing an HSTS is not to be taken lightly for anybody else new to it. With that said, I'm looking for advice on what can be done differently from those who have knowledge on .htaccess.

#IMPLEMENT HSTS
<IfModule mod_headers.c>
Header set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</IfModule>

#CUSTOM ERROR PAGES
ErrorDocument 400 /allerror.php
ErrorDocument 401 /allerror.php
ErrorDocument 403 /allerror.php
ErrorDocument 404 /allerror.php
ErrorDocument 405 /allerror.php
ErrorDocument 408 /allerror.php
ErrorDocument 500 /allerror.php
ErrorDocument 502 /allerror.php
ErrorDocument 504 /allerror.php

RewriteEngine On

#REDIRECT TO SECURE HTTPS CONNECTION
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

#FORCE WWW TO NON-WWW
RewriteCond %{HTTP_HOST} ^www.example.com [NC]
RewriteRule ^(.*)$ https://example.com/$1 [L,R=301]

#URL EXTENSION REMOVAL
RewriteCond %{THE_REQUEST} /([^.]+)\.html [NC]
RewriteRule ^ /%1 [NC,L,R]
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^ %{REQUEST_URI}.html [NC,L]

#HOTLINKING PROTECTION
RewriteCond %{HTTP_REFERER} !^https://(www\.)?example\.com(/.*)*$ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteRule \.(css|flv|gif|ico|jpe|jpeg|jpg|js|mp3|mp4|php|png|pdf|swf|txt)$ - [F]

#CONTENT SECURITY POLICY
<FilesMatch "\.(html|php)$">
    Header set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: 'unsafe-inline'; media-src 'self' data: 'unsafe-inline'; connect-src 'self';"
</FilesMatch>

#REDIRECT FOR DATE PAGE
RewriteRule ^date$ /storage/date-202010 [R=301,L]

#REDIRECT FOR HOME PAGE
RewriteRule ^home$ / [R=301,L]

#PREVENT DIRECTORY BROWSING
Options All -Indexes

#FILE CACHING
    #cache html and htm files for one day
<FilesMatch "\.(html|htm)$">
Header set Cache-Control "max-age=43200"
</FilesMatch>
    #cache css, javascript and text files for one week
<FilesMatch "\.(js|css|txt)$">
Header set Cache-Control "max-age=604800"
</FilesMatch>
    #cache flash and images for one month
<FilesMatch "\.(flv|swf|ico|gif|jpg|jpeg|mp4|png)$">
Header set Cache-Control "max-age=2592000"
</FilesMatch>
    #disable cache for script files
<FilesMatch "\.(pl|php|cgi|spl|scgi|fcgi)$">
Header unset Cache-Control
</FilesMatch>

#BLOCKS FILE TYPES FOR USERS
<FilesMatch "\.(ht[ap]|ini|log|sh|inc|bak)$">
Require all denied
</FilesMatch>

HSTS Implementation

To note a few things that I've learned while researching HSTS:

  1. An SSL certificate is required
  2. If your sites are available via HTTP, redirect all requests to HTTPS with a 301 Permanent Redirect.
  3. Include the following in your .htaccess file: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. Max-age must be at least 10886400 seconds or 18 Weeks. Go for the two years value.
  4. Add your domain to a preload list using the first link below.

I encourage everyone to read each source below for more information.

\$\endgroup\$
3
+50
\$\begingroup\$

There are two aspects to HTTP Strict Transport Security (HSTS):

  1. Implementing HSTS on your site.
  2. Submitting your already HSTS enabled site to the HSTS preload list. This is where the "list of HSTS sites" are compiled directly into the browser, which avoids the first request ever being over HTTP (where the request is potentially vulnerable to MITM attacks).

You seem to be going straight for #2. This is not necessarily recommended. Think of the "preload list" as a one-way trip. Technically it's possible to be removed from the preload list; realistically that's not something you want to even be thinking about. (It's hard - slow - enough stepping back from HSTS-only.)

The preload list submission page itself does not recommend going straight to "preload list submission". The recommendation is to increase the max-age parameter over a period of time (months), before taking the final step to submitting to the preload list. Test test test in the meantime to ensure SSL certs are reliably renewed, no mixed content warnings etc. etc.

I would also be wary about HSTS preload list submission (or even HSTS itself to some degree) on a shared server, where you don't have full control over the SSL configuration. You don't actually state whether you are on a shared server or not, but since you are doing all this config in .htaccess, I assume that you are. If you have your own server and access to the server config then most of this should be configured in the server/virtualhost config (and it is arguably easier and more reliable to do so).

Remember, that once you have gone the HSTS route (and users have accessed the HTTPS site or you are on the "preload list") then your site can only be accessed over HTTPS. This doesn't just apply to your site, but also to any 3rd party services you might be using (mixed content browser warnings etc.).

#IMPLEMENT HSTS
<IfModule mod_headers.c>
Header set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</IfModule>

This sets the required HSTS HTTP response header on "most"*1 responses (but note the preload parameter, which should probably be omitted initially).

*1 However, this directive does not necessarily set the required header on all responses. A requirement of HSTS is that you also set the header on "redirect" responses (eg. www to non-www on HTTPS). Currently the above does not do this. You should be using the always condition on the Header directive to set the header on responses other than 200 OK. For example:

# Use "always" condition to set on "redirects" as well.
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

(I've also removed the preload parameter for now.)

You do not need the <IfModule> wrapper and should be removed. mod_headers should be enabled by default. This is your server, you know whether mod_headers is enabled or not. mod_headers must be enabled for this to work. You don't want this to silently fail should mod_headers not be available - you need notification as soon as this fails with an error in your logs.

Many articles state that you should only set the Strict-Transport-Security header on secure (HTTPS) responses. And the "preload list submission" does indeed issue a "warning" (not strictly an "error" I believe) if you send the header over HTTP as well. However, whilst it only needs to be set on the HTTPS response, compliant browsers ignore this header when sent over an unencrypted HTTP connection (to prevent MITM attacks) so it should not matter whether the header is "unnecessarily" sent over HTTP as well. This would be easier to manage in the appropriate <VirtualHost> containers in the main server config. Sending this header only on HTTPS responses in .htaccess is more complex (and hence more prone to error). You would need to employ an additional environment variable that you can use to set the HSTS response header conditionally.

For example:

# Set environment var "HSTS" if accessed over HTTPS connection
RewriteCond %{HTTPS} on
RewriteRule ^ - [E=HSTS:1]

# Conditionally set only on HTTPS connections (ie. when "HSTS" env var is set)
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains" env=HSTS

Otherwise, I believe your remaining directives are OK with regards to HSTS. But as always, test test test.


Resolve multiple (unnecessary) redirects

#FORCE WWW TO NON-WWW
RewriteCond %{HTTP_HOST} ^www.example.com [NC]
RewriteRule ^(.*)$ https://example.com/$1 [L,R=301]

#URL EXTENSION REMOVAL
RewriteCond %{THE_REQUEST} /([^.]+)\.html [NC]
RewriteRule ^ /%1 [NC,L,R]

A potential issue here (with the 2nd and 3rd canonical redirects above, after the HTTP to HTTPS redirect) is that this potentially results in two additional redirects if www + .html is requested. This could be resolved by simply reversing the two redirects and including the canonical hostname in the "URL EXTENSION REMOVAL" redirect (as mentioned in my answer to your earlier question).

For example:

#URL EXTENSION REMOVAL
RewriteCond %{THE_REQUEST} /([^.]+)\.html [NC]
RewriteRule ^ https://example.com/%1 [R=301,L]

#FORCE WWW TO NON-WWW
RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
RewriteRule (.*) https://example.com/$1 [R=301,L]

See my previous answer for an alternative approach to the "URL EXTENSION REMOVAL" redirect that addresses some potential issues.

If you are not using the www subdomain on other hostnames (eg. www.subdomain.example.com) then you could simplify the CondPattern on the www to non-www redirect to simply ^www\., ie. any requested hostname that simply starts www., rather than checking the entire hostname.


Additional Fixes

URL Extension Removal / Rewrite Loop (500 Internal Server Error)

There are a couple of other issues that I did not get round to addressing in your earlier question, not relating to HSTS, which I'll cover below...

#URL EXTENSION REMOVAL
:
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^ %{REQUEST_URI}.html [NC,L]

This (and similar) is one of those code snippets that is "blindly" copy/pasted everywhere (and I mean everywhere) as the "standard" way to append (URL-rewrite) the file-extension when using extensionless URLs. However, whilst it probably "works" for your valid-URLs, it has a serious flaw when requesting invalid-URLs...

If /about.html is a valid file that you wish to serve when requesting the extensionless URL /about then it works OK. However, if I (maliciously) requested /about/ or /about/<anything> it will send your server into a spiralling rewrite-loop, resulting in 500 Internal Server Error response. The end-user should not be able to invoke such a response (potentially more vulnerable to DDOS attacks and other hostile behaviour).

This is because REQUEST_FILENAME (the mapped filesystem path) does not necessarily refer to the same public URL-path as the REQUEST_URI (requested URL-path) variable.

To resolve this, use REQUEST_URI throughout. For example:

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule ^ %{REQUEST_URI}.html [L]

(The NC flag is superfluous on the RewriteRule directive here.)

See my answer to the following ServerFault question for more detail on this.

Hotlink Protection - Simplify Condition

#HOTLINKING PROTECTION
RewriteCond %{HTTP_REFERER} !^https://(www\.)?example\.com(/.*)*$ [NC]

This is relatively minor. The regex in the above condition can be simplified. At this point in the .htaccess file, the hostname has already been canonicalised to remove the www the subdomain, so the (www\.)? subpattern in the above is superfluous. As is the trailing (/.*)*$ subpattern, which simply matches everything else. You don't need to actually match anything here, you just need to assert that the Referer header starts with the appropriate (scheme+)hostname.

For example:

RewriteCond %{HTTP_REFERER} !^https://example\.com

The NC flag is also superfluous here. Forcing a case-insensitive match when it's not required just creates (a tiny bit) more work for your server and in some cases can open you up to vulnerabilities ("duplicate content" being a common one - although that is not an issue here).

Enabling unnecessary Options

#PREVENT DIRECTORY BROWSING
Options All -Indexes

This doesn't only prevent "directory browsing". The All argument enables a bunch of other stuff that you probably don't need, such as server-side includes (Includes) and the ability to execute CGI scripts (ExecCGI). (Incidentally, this is the only time when you can mix arguments with a + or - with those without.) To only prevent directory browsing (ie. the auto generating of directory indexes by mod_autoindex) then remove the All argument.

However, you probably only need FollowSymLinks (which might already be set in the server config), so you could set the following instead:

Options FollowSymLinks

Note the absence of + or -. This only sets FollowSymLinks, so disabling Indexes ("directory browsing") if it was already set.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.