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.

HTTP security headers provide context and direction for client browsers to determine whether a requested HTTP resource might contain malicious components. As an example, when an attacker sends their target a malicious link containing an exploited cross-site scripting (XSS) vulnerability, a dedicated security header that whitelists JavaScript resources can prevent the XSS payload from executing. Put simply, HTTP security headers are the last line in a defense in depth strategy designed to protect users against malicious activity.

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.”

The Content-Security-Policy header defines the script, image, media, styling and font resources that a webpage is allowed to load/use/run. As you can imagine, explicitly telling the user’s browser to deny execution for any Javascript resources not specified by the developers would prevent a lot of common issues like XSS exploitation.

To explain how to implement it, let’s look at the content-security-policy of fortynorthsecurity.com:

FortyNorthSecurity.com's Content-Security-Policy

In general, the CSP is written in multiple statements, separated by semicolons, with each statement specifying the permitted locations for a particular type of resource (ex: image, font, javascript, etc.)  A statement first includes the resource type (script-src, style-src, font-src, media-src, etc.) and then the whitelisted (explicitly allowed) resource locations. Resource location values include the following:

  • “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

So, why did we caveat this header by saying that it is very difficult to implement? Unless all of your JavaScript, CSS and similar resources are neatly packaged and embedded within a folder within your application, you’re likely calling resources both inline and from third-party sites. To write an effective CSP policy, you need to track down all of those resources and whitelist each of them. The process of building the CSP will likely temporarily break some components within your pages, since forgetting to allow a resource will prevent it from loading and impact the page functionality. As a result, generating a CSP is time consuming, but please, please, please take the time to do it.


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:

Image from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security

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.

Chrome issues an "307" redirect when attempting to visit the HTTP version of a site with a cached HSTS policy

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.

Image from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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.

In the meantime, feel free to explore our past blog posts and if you ever need support in reviewing your security headers posture, contact us!