Using HTTPS in your web application is mandatory to guarantee trust and security. However, an attacker may try to downgrade that secure protocol into simple HTTP and grab or tamper with the exchanged data. Learn how this type of attack works and how you can prevent it.
What is an HTTPS Downgrade Attack
Nowadays, most websites and web applications use HTTPS as their default protocol. This happened thanks to the effort of browser providers and other supporters. Using a secure protocol like HTTP over TLS is a best practice for a few reasons:
- It guarantees confidentiality since data exchanged between the client and the server are encrypted.
- It ensures authenticity since the domain identity is verified by a trusted third-party (Certificate authorities).
- It offers data integrity since any tampering attempt is detected.
In other words, by using HTTPS, you avoid typical person-in-the-middle (man-in-the-middle) attacks, where an attacker intercepts and possibly alters messages exchanged between the client and the server.
However, even if you enabled your website to use HTTPS, there are situations that an attacker can exploit to downgrade the secure protocol to the unprotected HTTP. How can this happen?
Suppose your website is using HTTPS, but you missed updating a link in one of your pages, say the following one:
<a href="http://mywebsite.com/awesomepage">Awesome page</a>
This link uses plain HTTP. What happens if the user clicks that link and the HTTPS-enabled server receives a plain HTTP request? The typical approach is to redirect the client to use HTTPS, as shown by the following diagram:
However, an attacker may intercept that apparently harmless HTTP request and trigger the person-in-the-middle attack you are attempting to avoid with HTTPS. The following diagram shows how interception may occur:
From that request on, all messages sent by the client are transmitted via HTTP and manipulated by the attacker. One single HTTP request may compromise the whole communication security between the client and the server. For this reason, you should never mix HTTP and HTTPS protocol on a website.
To avoid this type of protocol downgrade, you should make sure that all the internal links in your website are relative or use HTTPS. This may be an affordable solution for small websites, but it is not feasible for a large website, especially if its content results from many people's contributions.
"Ensuring security by simply forcing HTTPS as the default protocol is not so trivial."
Tweet This
Using Content Security Policy
The HTTP Content-Security-Policy (CSP) header can help you force browsers to use HTTPS throughout your website. The upgrade-insecure-requests
directive is designed for this purpose. By adding the following meta tag in the head section of all the pages of your website, any link using HTTP will be interpreted as if it uses HTTPS:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
Consider that the directive also applies to non-navigational links, such as scripts, stylesheets, images, and other media.
Note: While major browsers support the
upgrade-insecure-requests
directive, it has never been implemented in IE11. Even if Microsoft's browser is now deprecated, it is still being used.
Alternatively, your server application can send the HTTP header to the browser. For example, in an Express application, you can send it through a middleware, as shown in the following code snippet:
const express = require("express");
// ...other require...
const app = express();
app.use(function(req, res, next) {
res.setHeader("Content-Security-Policy", "upgrade-insecure-requests");
next();
});
// ...other code...
An alternative to manually setting HTTP security headers in your server application is to use a specific package. For example, in an Express application, you can use helmet.
Note: By using the
upgrade-insecure-requests
, the HTTP fallback is automatically disabled. This means that if the requested resource is not actually available via HTTPS, the request will fail.
Using HSTS
There are situations where the CSP upgrade-insecure-requests
directive may not be enough. For example, this happens when a third-party website has a link to your website with the http
scheme. Actually, the upgrade-insecure-requests
directive tells the browser to use HTTPS for any further request originated by that page. However, the page itself is supposed to be requested through HTTPS. If it is requested via HTTP, the person-in-the-middle attack may still happen, and the attackers can manipulate the HTTP headers before they reach the browser. In other words, the CSP upgrade-insecure-requests
directive doesn't eliminate the risk of having HTTPS downgraded to HTTP.
You need a way to tell the browser to use HTTPS to request any resource of your website, not just the resources linked to the current page. This is the purpose of another HTTP header: the HTTP Strict-Transport-Security
header (HSTS). The following is an example of HSTS header received by a browser:
Strict-Transport-Security: max-age=31536000; includeSubDomains
When the browser receives such a header, it records the page's domain and the expiration time expressed in seconds through the max-age
directive. From now on, the browser will use HTTPS for any request sent to that domain, regardless of the specified protocol. This will happen until the expiration time elapses unless a new HSTS header is received. In fact, each time the browser gets a new HSTS header, the expiration time is updated.
In the example, besides the max-age
directive, you notice the includeSubDomains
directive. This directive tells the browser to apply the HTTPS request policy to any subdomain of the current page's domain. This increases the security of the whole domain hosting your website or application.
Preloading Strict Transport Security
So, by using the HSTS header, you ask the browser to use HTTPS for any request to your website for a given time. With this instruction, the browser will ignore any attempt to make requests via HTTP, wherever they come from. However, there is still one possibility that could cause HTTPS downgrade: the very first access to your website.
Suppose a user has never visited your website. Now, suppose they manually insert your website URL in their browser's address bar by explicitly using the http
scheme. Since this is the very first time the user makes a request to your website and their browser knows nothing about it, that request is vulnerable. Only after the first request with an HSTS header the browser knows that it always has to use HTTPS. An attacker may intercept the first request, and your website is compromised.
How can you instruct a browser to reach your website by always using HTTPS, including the very first time?
Well, you can request to add your domain to the HSTS Preload List. This domain list is maintained by Google and is hardcoded into Chrome as the list of sites that should be requested exclusively via HTTPS. Most major browsers also have their HSTS preload list based on the Chrome list.
You need to meet a few requirements to be included in the list. Among others, you have to add the preload
directive to the HSTS header as in the following example:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Once your domain is included in the hardcoded HSTS list of a browser, the browser will no longer make any requests to your website via HTTP.
Web APIs and HTTPS Downgrade Attacks
So far, the discussion about HTTPS downgrade concerns browsers. What about Web APIs?
You may apply HTTP-to-HTTPS redirection and HSTS headers, but API clients usually don't follow the redirect, unlike browsers. So, the only practical approach is to deprecate the HTTP protocol and use only HTTPS, as recommended by CIO.gov.
The main problem here is how to deal with possible HTTP requests. In case of migration from an HTTP-based API to HTTPS, you should apply a progressive transition involving the partners that have built clients for your API. Also, be explicit in the API documentation about using HTTPS. From a technical point of view, your API should return the 403 Forbidden HTTP status code to an HTTP request.
The following is a simple example of Express middleware that refuses HTTP requests:
const express = require("express");
// ...other require...
const app = express();
app.use(function(req, res, next) {
if (!req.secure && req.get('x-forwarded-proto') !== 'https') {
return res.status(403).send('Please, use HTTPS');
}
next();
});
// ...other code...
In the code above, you check if the current request is using HTTPS (req.secure
) or the original request used that protocol (req.get('x-forwarded-proto') !== 'https
). The latter check makes sense if your API is behind a reverse proxy. If the request is not using HTTPS, the server sends a 403 HTTP status code as a response.
About Auth0
Auth0 by Okta takes a modern approach to customer identity and enables organizations to provide secure access to any application, for any user. Auth0 is a highly customizable platform that is as simple as development teams want, and as flexible as they need. Safeguarding billions of login transactions each month, Auth0 delivers convenience, privacy, and security so customers can focus on innovation. For more information, visit https://auth0.com.
Conclusion
In this article, you learned how ensuring security by simply forcing HTTPS as the default protocol in your website or application is not so trivial. The person-in-the-middle attack may still happen in particular situations. However, you can leverage a few standard HTTP headers to mitigate this risk: the CSP upgrade-insecure-requests
directive and the HSTS header with preloading. You also learned that a slightly more crude approach should be used for Web APIs due to the lack of standard clients that can apply security policies similar to browsers'.
To learn more, visits the OWASP HSTS Cheat Sheet and the CIO.gov HTTPS adoption guidelines.