Adding Strict-Transport-Security (HSTS) HTTP Header In ColdFusion 2021
For years, I've been using Foundeo's HackMyCF security product on my server to help me keep my ColdFusion applications secure and up-to-date. Security is one of those feature that tends to rot over time. So, it's nice to have someone constantly nagging you about actively updating your platform. This morning, I'm finally adding the HTTP Strict-Transport-Security
response header (often abbreviated as HSTS) to my ColdFusion blog so that browsers will force connections to be made using HTTPS, never HTTP.
Even though I have logic in my ColdFusion application that automatically redirects users from the HTTP protocol to the HTTPS protocol, the initial request is still being made over a non-secure connection. And, according to the Mozilla Developer Network (MDN), this means that the first request is still vulnerable to attacks:
If a website accepts a connection through HTTP and redirects to HTTPS, visitors may initially communicate with the non-encrypted version of the site before being redirected, if, for example, the visitor types
http://www.foo.com/
or even justfoo.com
. This creates an opportunity for a man-in-the-middle attack. The redirect could be exploited to direct visitors to a malicious site instead of the secure version of the original site.
As such, we can use the Strict-Transport-Security
HTTP header to tell the browser to automatically convert requests over to HTTPS before they even leave the user's computer. This avoids the initial HTTP request altogether.
In ColdFusion, we can use the onRequestStart()
event handler in the Application.cfc
ColdFusion application component to add HTTP headers to every outgoing request. Here's an abbreviated version of my framework component:
component
output = false
hint = "I define the application settings and event handlers."
{
// Define application settings.
this.name = "WwwBenNadelCom";
this.applicationTimeout = createTimeSpan( 2, 0, 0, 0 );
this.sessionManagement = false;
this.setClientCookies = false;
// Force requests onto the WWW subdomain.
if ( cgi.server_name == "bennadel.com" ) {
redirectToWww();
}
// Force requests onto the HTTPS protocol.
if ( ! isSecureHttpRequest() ) {
redirectToSsl();
}
// ... truncated for demo ...
// ---
// LIFE-CYCLE METHODS.
// ---
/**
* I initialize every ColdFusion request.
*/
public void function onRequestStart( required string script ) {
// In production, the control-flow (in the pseudo-constructor) has already
// asserted that the current request is on the WWW subdomain and is being
// accessed over HTTP. Now, we just need to tell the browser to ALWAYS USE HTTPS.
// --
// According to MDN, a value of 63072000 (2 years) is necessary to be included in
// the preloading site cache for browsers.
var hstsValue = ( application.config.isLive )
? "max-age=63072000; preload"
: "max-age=0"
;
cfheader( name = "Strict-Transport-Security", value = hstsValue );
}
// ---
// PRIVATE METHODS.
// ---
/**
* I determine if the incoming request is being made over SSL.
*/
private boolean function isSecureHttpRequest() {
if ( cgi.https == "on" ) {
return( true );
}
var headers = getHttpRequestData( false ).headers;
var proto = ( headers[ "X-Forwarded-Proto" ] ?: "" );
return( proto == "https" );
}
/**
* I redirect to the SSL version of the current URL.
*/
private void function redirectToSsl() {
var nextUrl = ( cgi.query_string.len() )
? "https://#cgi.server_name##cgi.path_info#?#cgi.query_string#"
: "https://#cgi.server_name##cgi.path_info#"
;
location(
url = nextUrl,
addtoken = false,
statuscode = 301
);
}
/**
* I redirect to the WWW version of the current URL.
*/
private void function redirectToWww() {
var nextUrl = ( cgi.query_string.len() )
? "https://www.#cgi.server_name##cgi.path_info#?#cgi.query_string#"
: "https://www.#cgi.server_name##cgi.path_info#"
;
location(
url = nextUrl,
addtoken = false,
statuscode = 301
);
}
}
As you can see, my Application.cfc
already has logic to make sure that the user is accessing the WWW site over HTTPS. However, it's now also returning the Strict-Transport-Security
header to help ensure that the user never makes an HTTP request to my server in the first place.
When a user makes a secure request to the server, the HTTP headers now look like this:
Is a small update. But, continually making one small update after another is how we keep our ColdFusion servers secure and our users safe!
Why Not Add This HTTP Header via nginx / Apache / IIS / CloudFlare?
Most ColdFusion application servers sit behind some sort of a web server proxy like nginx, Apache, or Microsoft's IIS (Internet Information Service). And, many sites then also sit behind some sort of a CDN (Content Delivery Network) proxy. Each one of these proxies is capable of injecting HTTP headers into every response. So, you might be wondering why not just inject security-related HTTP headers there?
The reason: version control. Every aspect of a ColdFusion application that is moved outside of the ColdFusion code is one more thing that has to be remembered if the site is ever moved to a new host. By keeping security headers inside the CFML, we remove any opportunity to accidentally expose security vulnerabilities if-and-when the code is ever moved to a new location or put behind a different CDN.
Frankly, I don't trust me to remember these things. And, by making sure that security configurations are in the ColdFusion code and are being tracked in the git repository, then I get to remove the weakest link in the chain: Me.
Want to use code from this post? Check out the license.
Reader Comments
I think it is worth pointing out that using preload by default will make it more difficult to switch back to http if you ever need to.
Preload will put you on a list that is not easy to get off of.
https://hstspreload.org/#removal
@Craig,
You raise a good point. I also had concerns about this, which is why I did not include the
includeSubDomains
attribute in the header. I figured, if it's only for thewww
site, then I can be pretty sure that I can honor that. But, the info that you linked to talks about all the subdomains... which made me a bit nervous. So I started to read some more of that page it looks like maybe thepreload
stuff won't even be considered unlessincludeSubDomains
is also present.It's a little confusing, though. Maybe it would be best if I just remove the
preload
stuff. I don't feel that strongly about it.I ended up going back and removing
preload
based on Craig's comments and based on the fact that it looks like it won't mean much unless the subdomains are preloaded too, which I'm not ready to do at this time (since I don't have other subdomains).(Thanks for all you do for the community Ben!)
Can I ask why you wouldn't want Strict-Transport-Security enabled in dev too? What's the potential harm?
I'm asking out of general curiosity, and also because we have a upcoming-features preview and end-user-test site that's definitely not production, but it's accessible from outside, so it has production-level security.
@Dave,
My only concern was that I might not have an SSL certificate in my local environment. If I were running in something like a Docker'ized container, with nginx in front of ColdFusion, then it probably would have some sort of self-signed certificate. But, if I were running the local dev environment in something like CommandBox, I don't think an SSL cert would be available (should maybe it would be - those Ortus fellas think of everything). So, I was just cautious to say it should be enabled everywhere.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →