This is the fourth in a series of several posts on how to do way more than you really need to with Let's Encrypt, certbot
, and a good server. I use all of these things regularly but I've never taken the time to take them apart, look at how they work, and spend hours in Google trying in vain to figure out how to put them back together. It was inspired by a disturbing trend of ISP privacy violations and the shocking regulatory capture of the US Federal Communications Commission.
This post looks at a collection of useful security headers. I've tried to explain what each one does, where it can be helpful, and where it might bite you. None of these are absolutely necessary; if nothing else I strongly recommend using HSTS.
- The Series so Far
- Code
- Note
- Primary Security Reference
- Caveat
- Primary Header Config
- Force Secure Communication
- The Kitchen Sink
- Prevent Clickjacking (Historical)
- Cross-Site Scripting
- Content Sniffing
- Referer
- Primary Header Config Redux
- But What About...
- Before You Go
- Legal Stuff
The Series so Far
- Overview
- First Steps
- Tuning with OpenSSL
- Useful Headers
- Generating and Testing a Cert
- Automating Renewals
Things that are still planned but probably not soon:
- Updating OpenSSL
- CSP Playground
- Vagrant Examples (don't hold your breath)
Code
You can view the code related to this post under the post-04-useful-headers
tag. If you're curious, you can also check out my first draft.
Note
I'm testing out some new tooling. This will be wotw-highlighter
's shakedown run. Let me know what you think about the syntax highlighting! I'm pretty excited because (no whammies) it should work well in AMP and normally.
I wrote the majority of the Apache examples with httpd
in mind, i.e. from a RHEL perspective. If you instead use apache2
, most of the stuff should still work, albeit maybe just a little bit different.
Primary Security Reference
Originally, this post was sourced from a collection of personal experience and interesting sources found during writing. However, once I split this post out, I wanted to find some best practices (my code, while certainly practice, isn't necessarily the best). The Open Web Application Security Project maintains a list of useful headers, which should all be covered here.
Caveat
EVERYTHING HERE CAN BE SIDESTEPPED. Headers are sent with a request/response, which means they can be completely ignored. Headers do not prevent bad actors from doing malicious things. They do, however, force average users to do things as expected, which usually prevents bad actors from tricking average users into doing malicious things. This is a very important distinction.
Primary Header Config
I like to split the crypto config and header config. I'm always going to want to use a good algorithm, but I might not always want to use, say, X-Frame-Options
. YMMV.
As I said before, I like /etc/<server>/common/
, YMMV.
Nginx
$ sudo touch /etc/nginx/common/ssl-headers.conf |
Apache
$ sudo touch /etc/httpd/common/ssl-headers.conf |
Force Secure Communication
As previously mentioned, HSTS ensures users use secure protocols. The HSTS header, Strict-Transport-Security
, has three primary options:
-
max-age
: This specifies the maximum amount of time a user agent (browser) should cache the header. To make things easier, we'll give the cache a half-life of two years:Twitter uses 20 years. Most sites either use one or two years. Qualys wants to see at least 120 days.
-
includeSubdomains
: Without including subdomains, there are apparently some cookie attacks that can still be run. However, if you explicitly cannot serve subdomain content securely, this will cause problems. Err on the side of caution but check you subdomains. -
preload
: You can submit your HSTS site to an external list. This is a long-term commitment, so don't submit your site unless you're sure about config. I won't be using it here because of the extra steps, but I highly recommend it if you've got a stable setup.
HSTS will forcefully break your site if you don't have a proper TLS setup. Remember, it's cached by the user agent, not something you have control over. You can nuke it when necessary, but it is a hassle to do so.
Nginx
Append ; preload
if you're on the list;
1 |
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; |
Apache
Append ; preload
if you're on the list;
1 |
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains" |
The Kitchen Sink
A Content-Security-Policy
header can handle a majority of the other topics here. In theory, CSP defines a secure execution contract. In the past, that was certainly true; the recent spec addition of blackbox code makes it much less secure (e.g. a media policy covers media, not blackbox code that must be run prior to actually running media). That's a personal soapbox, though.
Good CSPs are fairly rigid and explicitly define as much as possible. As such, you might not be able to share them across sites like some of the other headers (i.e. maybe define this per site instead of in /etc/<server>/common/ssl-headers.conf
). For example, a website that serves all its own assets will have a different CSP than a website that uses assets from a CDN.
WARNING: CSPs might break everything if you don't know what you're doing (and if you do know what you're doing, change "might" to "most certainly will"). Luckily you can test things via Content-Security-Policy-Report
until you're confident with the policy. CSPs are awesome but require much more work than the deprecated headers they, in part, replace.
Sources
CSPs provide granular source definitions. The default-src
directive is used for anything not specified, so it's a great place to start secure:
1 |
default-src: 'self'; |
Sources themselves have lots of options.
*
allows anything, i.e. don't use this'self'
allows content from the same originexample.com
allows content fromexample.com
https:
allows anything over TLS'unsafe-(inline|eval)'
allows inline and dynamic execution and styling
CSP currently defines the following -src
directives:
- catch-all:
default-src
- JavaScript:
script-src
- stylesheets:
style-src
- images:
img-src
- AJAX, sockets, and events:
connect-src
- fonts:
font-src
- plugins:
object-src
- HTML5 media:
media-src
iframe
s and web workers:child-src
- form actions:
form-action
For example, suppose you're serving all your own content but need a Google font to maintain consistent styling.
1 2 3 4 5 6 7 |
# Unless specified, everything must come from this origin |
In Nginx,
1 |
add_header Content-Security-Policy "default-src: 'self'; style-src: 'unsafe-inline' 'self' https://fonts.googleapis.com; font-src: 'self' https://fonts.gstatic.com;"; |
In Apache,
1 |
Header always set Content-Security-Policy "default-src: 'self'; style-src: 'unsafe-inline' 'self' https://fonts.googleapis.com; font-src: 'self' https://fonts.gstatic.com;" |
If you're loading lots of external content, an explicit CSP might not be practical. It's always a good idea to specify as much as possible, though. For example, this allows anything over TLS with some caveats on not markup:
1 2 3 4 5 6 7 8 |
# Allow anything loaded securely |
Frames
CSPs provide two directives that are useful for frames: sandbox
and frame-ancestors
. The first adds extra security when serving explicitly embedded content; the second adds extra security to all content.
I actually couldn't find any good examples of a CSP sandbox
policy. All of the sources I found looked like the MDN CSP sandbox
page, with a note about how the CSP sandbox
mimics the iframe
sandbox
attribute and a list of possible values. Without usage examples, my assumption is that it exists to provide an extra layer of sandbox
security. Anyone can strip the sandbox
tag from an iframe
or change its values; by setting it in the header itself you can limit the options available to external consumers.
I Made This
I was trying to figure out how everything worked together, so I built a small tool to play with everything together. It's really interesting stuff, especially if you do the ad thing. I also split off everything you need to ruin CSP for a quick reference. You should just be able to clone and go.
Prevent Clickjacking (Historical)
Note that this is superceded by a solid Content-Security-Policy
.
iframe
s make everything difficult. One of the simplest possible attacks is to drop your content into an iframe
and snoop the interaction. It's not always malicious; some people always try to embed things (still, in 2017) so they can do their own thing. The X-Frame-Options
header gives you iframe
control in user agents that support it.
The majority of websites don't want to be embedded and should probably use deny
, which prevents user agents that respect the header from embedding it. Some sites embed their own content but do not want others to embed it, which is captured by sameorigin
. Finally, you can allow a single external site to embed your content via allow-from example.com
.
Nginx
1 |
add_header X-Frame-Options "deny"; |
Apache
1 |
Header always set X-Frame-Options "deny" |
Cross-Site Scripting
XSS is a pretty neat little industry. I know a couple of guys that are still collecting income on exploits they found years ago. Creating exploits requires a lot of ingenuity and even more time.
Which means you should go out of your way to prevent it. No matter how clever you think you are, there's always someone smarter. More importantly, there's always a fresh cadre of new script kiddies that do things you've never thought of. As your codebase ages, low-hanging fruit like XSS headers are more useful than you might think.
Nginx
1 |
add_header X-XSS-Protection "1; mode=block"; |
Apache
1 |
Header always set X-XSS-Protection "1; mode=block" |
Content Sniffing
Multipurpose Internet Mail Extension types are really easy to pass around. They're fast to use and there are so many of them. However, they're equally easy to take advantage of.
Nginx
1 |
add_header X-Content-Type-Options "nosniff"; |
Apache
1 |
Header always set X-Content-Type-Options "nosniff" |
Referer
This is one of my favorite computer obstinacies, close to \t
in Makefiles. Typos aside, messing with the Referer header is both great and bad:
- You should try to protect the privacy of your users as much as possible. You don't need to know where they came from and you don't need to tell anyone else when they leave.
- Most of the internet works off the Referer header now. I've tried at various stages to get away from it without any luck. I might not want everything I do bought and sold by data firms and ad shops, but they don't really care and it's not going away any time soon.
You can beef up the Referer if you'd like, but you should do some serious testing on your apps first to make sure you won't be shooting yourself in the foot.
Primary Header Config Redux
I've left out things that could be problematic everywhere. You might need to consider tweaking X-Frame-Options
if your content gets embedded.
Nginx
/etc/nginx/common/ssl-headers.conf |
|
1 2 3 4 |
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; |
Apache
For the n
th time, I'd like to reiterate that I haven't actually tested this config. I will. Eventually.
/etc/httpd/common/ssl-headers.conf |
|
1 2 3 4 |
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains" |
But What About...
Public Key Pinning
I didn't include HTTP Public Key Pinning because it's pretty easy to screw up. As Let's Encrypt certs aren't necessarily as stable as commercial alternatives (i.e. may change more frequently without manual intervention), I want to do more research on this.
Cross-Domain Policies
I spent thirty minutes trying to come up with a good reason for a crossdomain.xml
policy. If you're not doing anything big with Flash or PDFs, I just don't see why you'd bother, especially with a good CSP. Personally, I'd recommend either none
or master-only
if you need a policy at all.
Before You Go
Let's Encrypt is a fantastic service. If you like what they do, i.e. appreciate how accessible they've made secure web traffic, please donate. EFF's certbot
is what powers my site (and basically anything I work on these days); consider buying them a beer (it's really just a donate link but you catch my drift).
Legal Stuff
I'm still pretty new to the whole CYA legal thing. I really like everything I've covered here, and I've done my best to respect individual legal policies. If I screwed something up, please send me an email ASAP so I can fix it.
- The Electronic Frontier Foundation and
certbot
are covered by EFF's generous copyright. As far as I know, it's all under CC BY 3.0 US. I made a few minor tweaks to build the banner image but tried to respect the trademark. I don't know who thecertbot
logo artist is but I really wish I did because it's a fantastic piece of art. - Let's Encrypt is trademarked. Its logo uses CC BY-NC 4.0. I made a few minor tweaks to build the banner image but tried to respect the trademark.
- I didn't find anything definitive (other than EULAs) covering Nginx, which doesn't mean it doesn't exist. Assets were taken from its press page.
- Apache content was sourced from its press page. It provides a full trademark policy.