FTDNA Sales Banner
Ads and affiliate links like this help support the Q-M242 project's efforts to test indigenous peoples. Please read the affiliateship and business disclaimers for details. You may also donate directly here.

GDPR & The Genealogy Blogger in 2019 – Security Headers

Security headers are technical. Most of us have likely never heard of them. They are the instructions we as site owners give users’ browsers to secure content from our website. It is the job of modern web browsers to be secure. Browsers allow site authors a good deal of freedom though in how secure their content is. They are as secure in some places as we tell them to be and not more than that.

This security is important, because it protects our readers.

I am going to cover the headers and how to set them. My own site is currently an Apache server, so I cannot test how to make the changes on Nginx (engine-X) or other platforms. I will do my best to include links to instructions for those in the resource links at the end. If someone sees an error, please speak up.

Note that before starting through this post you should already have moved your site to HTTPS.

There is a very good chance if you use Tumblr or Blogger that just turning on HTTPS fixed all of your headers. Run your site through Mozilla Observatory to check.

If you are using Apache, then these changes can all be done in your .htaccess file. Yes, some could be done in PHP.ini, but not everyone has access to that. For example, my cPanel account has some PHP.ini editing locked down for security. I could change it, but it might be lost in a system update or restart. Thus, .htaccess is a simpler go-to.

All .htaccess editing can break your site. Make one change at a time. Hit save. Reload your site. Undo the change if it broke your site. Feel free to post what broke in the comments. We can all get through this together.

HTTP Public Key Pinning (HPKP)

This one is hard. It is not essential. You can read about the reasoning behind Public Key Pinning here. You can read about why not to do it here. I do it, because my certificate only renews once a year.

The basics are this. We should be able to rely on DNS (the phone directory of the internet) and CA (secure certificate authorities) providers to keep things clean on their end. However, they have proven that they cannot be relied on. Thus, we have HPKP to have trust despite them.

Go to a standard SSH terminal for your web server. Enter this command replacing haplogroup.org with your own site name.

openssl s_client -servername haplogroup.org -connect haplogroup.org:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

You should get back something like this.

epth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 C = BE, O = GlobalSign nv-sa, CN = AlphaSSL CA - SHA256 - G2
verify return:1
depth=0 OU = Domain Control Validated, CN = *.haplogroup.org
verify return:1
YoMH0c+9u5HdSx9rsNrKI/rqy9d2/cljrUxrRNRl0QU=

That last line is the code you need.

You need to enter it in your .htaccess file. There are two versions of the Public Key pins report. One reports only. It is for testing. The other blocks access to your site unless everything checks out. It is for final use.

For the first week or so, we want to use the testing version, Public-Key-Pins-Report-Only call and not the Public-Key-Pins call. This is to test how the command works without risking the site breaking.

Long term, there are supposed to be two keys, the active and the backup. Because I don’t have a backup, I am using a random second one to make the header work.

Because this is testing, I have set the max-age for caching the information to only 60 seconds.

There is a website, https://report-uri.com, where you can sign up for a free trial account and get the testing reports. They can generate a URL to use in the report-uri field. Note that because this code uses quotes within quotes we have to place \ in front of each inner quote to escape it.

<IfModule mod_headers.c>
Header always set Public-Key-Pins-Report-Only "pin-sha256=\"YoMH0c+9u5HdSx9rsNrKI/rqy9d2/cljrUxrRNRl0QU=\"; pin-sha256=\"klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=\"; max-age=60; includeSubDomains; report-uri=\"https://haplogroup.report-uri.com/r/d/hpkp/reportOnly\""
</IfModule>

Set-Cookie

This one is quick and easy. You are forcing the contents of your session cookies, the essential ones, to be secure and to only be available on our own site. We all use the same code.

<IfModule mod_headers.c>
Header always edit Set-Cookie (.*) "$1;HttpOnly;Secure;SameSite=strict"
</IfModule>

Strict-Transport-Security

This is the command that tells browsers they should only access the site using HTTPS methods. You actually have to tell browsers this even thought you have enabled HTTPS elsewhere. There are three commands. Max-age is how long the browser should store the information before checking again. IncludeSubDomains tells the browser to include subdomains under your domain in the rule. This is useful for someone who has both blog.domain.com and www.domain.com. Preload which tells Google’s HSTS service to proload the rule for your domain.

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

Vary

This is not really a security header, but it gets set in the same section of your .htaccess file. It tells the browser how to cache information. You can read more about it here.

<IfModule mod_headers.c>
Header unset Vary
Header set Vary "Accept-Encoding"
</IfModule>

Referrer-Policy

This gives the browser permission to pass on to the next website that the user came to them from your site. For example, if you click on a link from my site to 23andMe, they can tell using most server and analytics tools that you got to their site from my site. Their are several options on how and when to allow this. Right now, I have set my site to allow it when the destination site is also HTTPS. Old HTTP sites do not get the information.

<IfModule mod_headers.c>
Header always set Referrer-Policy "no-referrer-when-downgrade"
</IfModule>

X-Content-Type-Options

I have trouble finding words here, so I am going to quote Mozilla directly on this one.

“The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This allows to opt-out of MIME type sniffing, or, in other words, it is a way to say that the webmasters knew what they were doing.”

<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
</IfModule>

X-Frame-Options

This tells a browser not to allow another website to show an embedded copy of your site on their site. This is an older form of content theft. Setting it to sameorigin means that you don’t allow it.

<IfModule mod_headers.c>
Header always set X-Frame-Options "sameorigin"
</IfModule>

X-XSS-Protection

This helps to prevent cross site scripting exploits by telling the browser to stop loading the site if one is detected. If you have a really good CSP policy, modern browsers don’t need it. However, a really good CSP policy is almost impossible without breaking WordPress.

<IfModule mod_headers.c>
Header set X-XSS-Protection "1; mode=block"
</IfModule>

X-Permitted-Cross-Domain-Policies

This sets whether PDF files and Adobe Flash files can load content from your site. In general, the answer is always going to be no. If someone else wants to use our content, they should come to us. Mostly, they should link people to our sites.

<IfModule mod_headers.c>
Header set X-Permitted-Cross-Domain-Policies "none"
</IfModule>

Feature-Policy

The feature policy sets the rules for modern browsers using newer technologies like the computer’s camera and microphone. You can set policy for both your site and any items you embed such as YouTube videos and Google Spreadsheets. This is one to pay attention to as new technologies come out.

<IfModule mod_headers.c>
Header always set Feature-Policy "geolocation 'none'; midi 'none'; microphone 'none'; camera 'none'; magnetometer 'self'; gyroscope *; speaker *; fullscreen 'self'; payment 'none';"
</IfModule>

Content-Security-Policy (CSP)

This is where you set what content such as JavaScript can load and run. Ideally, you would set a really strict policy. It is a challenge to set up. You basically set it as tight as you can, load your site in a browser with the developer tools showing the counsel, and see what breaks. You fix what broke, and you keep reloading different site pages until you find all of the break points.

I hate this one. It really outs how sloppy insecure the code on the admin side of WordPress is.

<IfModule mod_headers.c>
Header add Content-Security-Policy "default-src 'self' 'unsafe-inline' data: https://*.haplogroup.org; \
connect-src 'self' 'unsafe-inline' https://*; \
base-uri 'self'; \
font-src 'self' data: 'unsafe-inline' https://*; \
form-action 'self' data: https://*; \
frame-src 'self' data: https://*; \
img-src 'self' data: https://*; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*; \
script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' https://*; \
style-src 'self' 'unsafe-inline' https://*; \
style-src-elem 'self' 'unsafe-inline' https://*;"
</IfModule>

Recap

All Code For Testing

<IfModule mod_headers.c>
Header always set Public-Key-Pins-Report-Only "pin-sha256=\"YoMH0c+9u5HdSx9rsNrKI/rqy9d2/cljrUxrRNRl0QU=\"; pin-sha256=\"klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=\"; max-age=60; includeSubDomains; report-uri=\"https://haplogroup.report-uri.com/r/d/hpkp/reportOnly\""
Header always edit Set-Cookie (.*) "$1;HttpOnly;Secure;SameSite=strict"
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header unset Vary
Header set Vary "Accept-Encoding"
Header always set Referrer-Policy "no-referrer-when-downgrade"
Header set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "sameorigin"
Header set X-XSS-Protection "1; mode=block"
Header set X-Permitted-Cross-Domain-Policies "none"
Header always set Feature-Policy "geolocation 'none'; midi 'none'; microphone 'none'; camera 'none'; magnetometer 'self'; gyroscope *; speaker *; fullscreen 'self'; payment 'none';"
Header add Content-Security-Policy "default-src 'self' 'unsafe-inline' data: https://*.haplogroup.org; \
connect-src 'self' 'unsafe-inline' https://*; \
base-uri 'self'; \
font-src 'self' data: 'unsafe-inline' https://*; \
form-action 'self' data: https://*; \
frame-src 'self' data: https://*; \
img-src 'self' data: https://*; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*; \
script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' https://*; \
style-src 'self' 'unsafe-inline' https://*; \
style-src-elem 'self' 'unsafe-inline' https://*;"
</IfModule>

All Code After Testing

<IfModule mod_headers.c>
Header always set Public-Key-Pins "pin-sha256=\"YoMH0c+9u5HdSx9rsNrKI/rqy9d2/cljrUxrRNRl0QU=\"; pin-sha256=\"klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=\"; max-age=60; includeSubDomains; report-uri=\"https://haplogroup.report-uri.com/r/d/hpkp/reportOnly\""
Header always edit Set-Cookie (.*) "$1;HttpOnly;Secure;SameSite=strict"
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header unset Vary
Header set Vary "Accept-Encoding"
Header always set Referrer-Policy "no-referrer-when-downgrade"
Header set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "sameorigin"
Header set X-XSS-Protection "1; mode=block"
Header set X-Permitted-Cross-Domain-Policies "none"
Header always set Feature-Policy "geolocation 'none'; midi 'none'; microphone 'none'; camera 'none'; magnetometer 'self'; gyroscope *; speaker *; fullscreen 'self'; payment 'none';"
Header add Content-Security-Policy "default-src 'self' 'unsafe-inline' data: https://*.haplogroup.org; \
connect-src 'self' 'unsafe-inline' https://*; \
base-uri 'self'; \
font-src 'self' data: 'unsafe-inline' https://*; \
form-action 'self' data: https://*; \
frame-src 'self' data: https://*; \
img-src 'self' data: https://*; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*; \
script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' https://*; \
style-src 'self' 'unsafe-inline' https://*; \
style-src-elem 'self' 'unsafe-inline' https://*;"
</IfModule>

That is it for now.

Posts in Series

Sources & Resources

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

you're currently offline