HTTP Security Checklist

This entire page is based on web security guidelines from Mozilla Infosec, the HTTP documentation from Mozilla as well as documentation from OWASP. Due to this the content on this page is licensed under CC BY-SA 4.0.

HTTP Strict Transport Security (HSTS)

TL:DR; A web server which enforces HSTS will instruct web clients to force HTTPS on all outgoing requests, even if they are towards another domain.

The purpose of HSTS is to protect connecting web clients against downgrade attacks, from HTTPS to HTTP (see: Cloudflare). This could be performed by redirecting the legitimate user from the legitimate HTTPS server to another malicious HTTP (non-TLS) web server.

HSTS protects against this because it is actually a HTTP header which tells the browser to

  1. Force TLS on all HTTP requests (to HTTPS)

  2. Perform stricter error handling which regard to TLS

In addition, HSTS requires the web sever to also respond with a max-age header (which must be at least 6 months) which the browser will use to remember for how long HSTS should be used.

Heads up: If a browser connects to your web server with HSTS and you then disable it, the browser will not be able to connect until either (1) 6 months has passed or (2) HSTS is activated again.

Due to this TLS is a pre-requisite to HSTS and TLS must remain enabled at all times!

X-Frame-Options

TL:DR; A web client connecting to a web server, which instructs the client to use X-Frame-Options, will not allow the client to render the web page in an HTTP frame to protect the user from clickjacking attacks when browsing domains.

HTTP frames such as <iframe> has historically been used with malicious intent through Clickjacking attacks and in order to protect against them X-Frame-Options should be used. The purpose of X-Frame-Options is to prevent this with respect to HTTP frames. The reason this is relevant is that it its otherwise possible to not just render the content of a domain but also change it's appearance.

This makes it possible to make the frame transparent and place it on top of other content. In this manner, a <button> in the invisible frame which performs, for example, a bank transaction could be aligned on top of another button which the user is compelled to press in the malicious web page.

X-Frame-Options protects against this because, In practice, when a web browser connects to a web server, the server replies with a X-Frame-Options HTTP header which either (1) denies any framing or (2) allows framing but only for the same domain which the web server made the reply for.

Cross-Site Request Forgery (CSRF) Protection

TL:DR; A web browser is logged into a website, another website which the user visits may be able impersonate the user and thus perform actions as the user unless a CSRF protection mechanism is implemented.

When a user visits a website and log into their account, the web browser needs to store the something which from which the web server can derive the user's identity from. Often websites uses HTTP cookies to store a Session ID which acts as a representation of that the specific user is currently logged in.

In as long as that cookie exists the web browser will send that cookie to the web server per default in every following request to the domain which the web server is listening to. This creates the perfect scenario to perform a CSRF attack.

Heads up: This attack is not limited to cookies. CSRF attacks could be performed in other conditions where a trust is established between a web browser and a web page.

The idea is to wait for the user to log in and then somehow make their web browser perform a malicious request towards that web page. In practice this can be done by for example making the user visit a malicious web page which in the JavaScript submits an HTTP form to the website as the user visits the malicious web page. Such form is illustrated in the following example from OWASP where the user is logged into http://bank.com/transfer.do.

<body onload="document.forms[0].submit()">
  <form action="<nowiki>http://bank.com/transfer.do</nowiki>" method="POST">
    <input type="hidden" name="acct" value="MARIA" />
    <input type="hidden" name="amount" value="100000" />
    <input type="submit" value="View my pictures" />
  </form>
</body>

To protect against this, the bank website needs to implement CSRF protection. This can be done by several methods such as CAPTCHA or requiring that each Anti-CSRF Tokens.

TL:DR; A web server instructs a web browser to store information in a cookie will not protect from information leakage unless the default configuration is overridden.

HTTP Cookies are stored by web browsers by instruction of the web server. The configuration of how the cookie is accessed by the browser and sent to other web servers is also defined by the web server which sent the instruction, not the browser. As such there are several settings in the configuration which needs to be taken into account.

Secure

By default there are no restrictions on how whether cookies can be sent over an unencrypted HTTP connection. Often we only want to transmit the cookie if the connection uses TLS which is exactly what the secure directive does! It allows the web browser to be instructed to prevent sending the cookie.

HttpOnly

By default the browser will allow cookies to be accessed two main manners, outgoing HTTP requests and JavaScript. The HttpOnly directive instructs the web browser to only allow cookies to be accessed for outgoing HTTP requests. Why is this important you might ask? Most often the JavaScript which a web page executes is not written entirely by ourselves but by a third party. Since we also often store sensitive credentials in the cookie any script will be able to access the cookies using document.cookie and steal it unless the HttpOnly directive is set.

Domain

By default, when the domain directive is not set, only the same domain which instructed the web browser to set the cookie can access it. This is most often the preferred usage.

Heads up: By setting the domain=domain.tld directive you actually allow all subdomains to domain.tld to access the cookie. If you are not entirely in control of the DNS this usage should be used with caution as malicious.domain.tld would also be able to access the cookie.

By using the domain directive, the web browser permits other domains and its subdomains to access a cookie. If you do not want the subdomains to access the cookie, use the hostOnly=true directive!

SameSite

The SameSite directive instructs whether the web browser should send cookies in cross-origin requests. The purpose of the directive is to offer (some) protection against CSRF attacks. The directive can be set to three distinctive values: Strict. Lax and None.

Note: The SameSite directive demands that Secure=true is also used. The directive will not have any effect otherwise.

  • Strict: Will not send the cookie with any cross-origin requests and only sent to same-site requests. If you are on foo.com which has a <a href="bar.com"> link which the user clicks, no cookies will be sent to bar.com. As such this is more suited for high security environments such as bank sites but not your average web page. If facebook.com implements SameSite=Strict it means that if you have previously logged into Facebook you will be prompted to login again if you arrive from a google search since the Facebook cookie with your login credentials is not sent.

  • Lax: Similar to Strict but will allow cross-origin HTTP GET requests. This means that it would be possible to login to Facebook in the example above with SameSite=Lax when the user clicks on a <a href="facebook.com"> link. Note that Lax still does not send cookies for AJAX, iframe, or any other request type except pure HTTP GET requests.

  • None: The cookie is sent with any cross-origin request whether it's pure HTTP requests, AJAX, iframe etc.

This is a relatively new feature and according to Mozilla major web browsers are implementing the directive to default to Lax from previous default None. If you want to read more Heroku has a great post on what the SameSite directive means and why it is important.

X-Content-Type-Options

TL:DR; A web browser which receives a resource with incorrect MIME type will try to identify it in a manner which could be exploited. As a preventive measure, the web browser should be instructed to reject resources with absent or incorrect MIME type.

The X-Content-Type-Options header specifies how the web browser should handle resources such as stylesheets and scripts which have either an incorrect MIME type or no explicitly defined MIME type.

The usual behavior when this occurs is that the web browser tries to guess the MIME type in a manner which is implemented differently depending on the browser vendor. By using the header with the nosniff directive, the browser is instructed to not load any resources which does not have an explicit MIME type, thereby protecting the end-user against attacks which exploit incorrect MIME type identification.

Note: X-Content-Type-Options has only one directive: nosniff. This means that the header can be regarded as a boolean where the header is either set with the nosniff directive or is not set at all.

Content Security Policy (CSP)

TL:DR; A web client connecting to a trusted web server, which instructs the client to use CSP, will not trust JavaScript originating from other domains unless explicitly specified.

The purpose of CSP is primarily to both prevent and detect Cross Site Scripting (XSS) attacks where the web client connects to a trusted web server but loads JavaScript from another domain by, for example, using HTML tags such as <script src="malicious.domain.com"> .

In this regard, since the web client trusts the reply of the trusted web server, it will trust any content even though it is served by another domain and potentially malicious web server.

With CSP, the trusted web server instructs the web client to only execute scripts which originate from a predefined set of domains. This creates the ability to whitelist domains and thereby change the default trust-behavior to not trust any JavaScript unless its domain is whitelisted.

Cross-Origin Resource Sharing (CORS)

TL:DR; A web server which enforces CORS will not accept any script-initiated requests originating from a web client which is connected to a web server on any other domain unless explicitly specified.

The purpose of CORS is to limit malicious cross-domain requests and control how and from where a web server can be accessed by a client running code from a different web server.

Specifically, CORS is only applicable for cross-origin HTTP requests which occurs when all the following conditions are satisfied.

  1. A non-user initiated request is being sent using scripts such as with AJAX or XMLHttpRequest.

  2. The request (1) is a result of the browser executing JavaScript which was received from a web server on domain A.

  3. The request (1) destination is towards another domain B.

In the example above, domain B receives a cross-origin request and thus have the ability to allow/deny the request with CORS policies using the Access-Control-Allow-Origin HTTP header. This will instruct the web client if a content from the domain A (or any domain) is allowed to make request to domain B.

You can read a lot more about CORS in the Mozilla Developer Docs.

Subresource Integrity

In many cases, the JavaScript which executes in the web browser is not retrieved from the web server but from an external hosting server such as a CDN.

There are two directives which are relevant here, integrity and crossorigin.

<script
  src="https://code.jquery.com/jquery-3.5.1.min.js"
  integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
  crossorigin="anonymous"></script>

HTTP Public Key Pinning (HPKP)

Heads up: Public key pinning is considered to only be a justifiable option on domains which are among the highest risk levels. With this in regard it is most likely not the first security control to implement.

The idea behind public key pinning is that a web client which, on a regular basis, connects to a given domain should verify not only that the certificate of the domain is signed by a trusted Root Certificate Authority (CA), but also which specific CA.

The purpose of HTTP Public Key Pinning is to protect the client, which has previously visited your site, from communicating with your domains in the event that the CA has changed unexpectedly. While there may be a legitimate reason behind this it may also be a result of a malicious act by an adversary.

HTTP Public Key Pinning can be a powerful tool but it is however not the most popular option as it emphasizes confidentiality and integrity over availability.

Other considerations

Other security features worth looking at

Last updated