Skip to main content
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Andy Matthews and Jason Dean and Todd Rafferty
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Andy Matthews@commadelimited ) , Jason Dean@JasonPDean ) , and Todd Rafferty@webRat )

Adding Strict-Transport-Security (HSTS) HTTP Header In ColdFusion 2021

By on
Tags:

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 just foo.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:

Network activity in Chrome Dev Tools showing the Strict-Transport-Security HTTP header being returned.

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

15,260 Comments

@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 the www 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 the preload stuff won't even be considered unless includeSubDomains 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.

15,260 Comments

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

1 Comments

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

15,260 Comments

@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!

Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.