Quick Guide to Security Headers - Part One
A month ago, we finished a series of six web application assessments for local and regional banks. In addition to common web vulnerabilities, like SQLi, we found that nearly every bank failed to implement HTTP server security headers. After authoring six separate reports with nearly identical recommendations, we decided to create a quick reference guide for both our clients and other pen testers.
What is a Security Header?
We’re all familiar with HTTP headers. When you request a web resource, your browser will typically send information like your User-Agent and stored Cookies to enable a personalized response from the server. When the server replies, it will send varying headers, typically including Response Status, Content-Type and Server information at a minimum. This two-way interaction forms the base of our web browsing experience. Unfortunately, without additional instructions from the server about how to handle the incoming content, our browsers form a pretty wide-open attack surface.
Top Three Security Headers
In this post, we’ll cover the three arguably most important headers. Next post, we’ll cover four more and wrap up our discussion.
Ok. We’re going to be upfront about this one. This is the hardest to implement, but the most important.
Pulling directly from Mozilla’s web docs
“Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement to distribution of malware.”
To explain how to implement it, let’s look at the content-security-policy of fortynorthsecurity.com:
- “self”, which refers to assets within the application’s own file structure
- external domains, which are syntactically not surrounded by quotes and can specify a particular file or any file under a particular domain
- “unsafe-inline”, which allows inline resources, like inline CSS
- “eval”, which should never be used
In a nutshell, this header tells user browsers that the web application should only be accessed using HTTPS. Syntax is rather simple, as shown on Mozilla’s Web Docs:
The max-age value is typically set to one year (31536000), although you can select any value. If all of your subdomains should be served over HTTPS, then “includeSubDomains” should be added after a semicolon. The “preload” feature brings up an interesting and initially-unexpected aspect of this header.
If you’ve visited a website before and your browser has cached the strict-transport-security (HSTS) value, then you’ll automatically be redirected to the secure version every time you visit it until the HSTS value runs out (which it likely won’t). In this scenario, an attacker cannot MITM your browser and force it to display an HTTP version of the site.
However, if you’ve never visited a site before and navigate to the unsecure HTTP version, then a MITM attacker could control where you are redirected. As a result, the strict-transport-security header is ignored. Wait. What? Yes, the browser will ignore the HSTS header when you first visit a site in HTTP, since the browser knows that an attacker could trivially manipulate the response header.
If you’re like me, then you immediately wonder why we would even want to use this header. First, it’s super easy to implement. Second, for all users that previously visited your site securely, this protects them from MITM attackers. Third, the “preload”* HSTS value allows web developers to submit their sites for inclusion on browser preload lists, which effectively forces all users to visit your site using HTTPS. This is not part of the official RFC, so there is a little bit of Google black-box control over the process, but generally including the “preload” term in this header (and submitting your site to the preload service) will enforce HTTPS from the first time a user accesses your web application in their browser.
*Be careful with preloading, since undoing preload submission is difficult.
This header is all about preventing clickjacking. What is clickjacking? An attacker-controlled webpage loads a targeted application (think banking portal or email) in a hidden iframe below a visible button or link. When an unsuspecting user navigates to the attacker’s webpage and clicks that link, the attacker hijacks that “click” for execution on the application loaded in the iframe.
An easy way to prevent attackers from using your web application in clickjacking is to prevent it from loading in a frame, iframe, embed or object tag. The syntax is rather simple: “X-Frame-Options: deny”. If you need to allow rendering of a page on your own web application in one of those tags, then you can provide the value “sameorigin” instead of “deny”.
We’d like to note that while X-Frame-Options is still used by many, many web applications, Content-Security-Policy headers can be updated to include a frame-ancestors tag that will accomplish the same clickjacking prevention as X-Frame-Options.
Other Security Headers
There are several other security headers, including “X-Content-Type-Options”, “Referrer-Policy”, “Feature-Policy” and “X-XSS-Protection”. We’ll get to those in part 2 of this post.