Never output anything to a browser without using a formatting filter

May 11, 2016 · Chris Peters

Cross-site scripting (XSS) vulnerabilities can be quite a serious problem if you’re not careful. And if you’re using a framework like CFWheels, you need to be extra careful to protect your output from rendering malicious content.

Cross-site scripting (XSS) vulnerabilities can be quite a serious problem if you’re not careful. And if you’re using a framework like CFWheels, you need to be extra careful to protect your output from rendering malicious content.

This is an example of a fairly common CFWheels view:

<cfoutput>
<h1>
#linkTo(text=post.title, route="post", key=post.key())#
</h1>
<p class="post-meta">
#post.publishedAt#
</p>
<h2>#post.commentsCount# Comments</h2>
</cfoutput>
view raw show-unsafe.cfm hosted with ❤ by GitHub

It looks innocent enough, right? Plain vanilla CFML with the beloved pound signs for output. (I really do love CFML templating, one of the best parts of the CF platform in my opinion.)

The problem is what this script can ouput if a user started entering malicious content or if the database were hacked to contain malicious content:

<h1>
<a href="/posts/fart"><script>alert('All your base are belong to us!');</script></a>
</h1>
<p class="post-meta">
<script>sendCookieInfoToAnotherServer = function() { /* ... */ }; sendCookieInfoToAnotherServer();</script>
</p>
<h2>Banana Comments</h2>

Now your users’ cookie information is sent off to some hacker’s server. As my 19-month old daughter would exclaim, Oops!

How to avoid getting hacked (in this way at least)

To avoid this issue, you need to form the habit of running every piece of output through a formatting filter, no matter what it is and how much you “trust” it.

What do I mean by formatting filters?

  • EncodeForHtml (or HtmlEditFormat if you’re using pre-CF10)
  • DateFormat
  • NumberFormat
  • DollarFormat

A bonus of NumberFormat is that it’ll add commas as thousands, millions, billions, etc. separators without your needing to pass any additional arguments. Of course, you can override this in any way that you please with the mask argument.

And there are a few honorable mentions that you may end up needing as well (all requiring CF10+ or Lucee):

  • EncodeForCss
  • EncodeForJavaScript
  • EncodeForHtmlAttribute

What I’m suggesting is that you must always use one of these functions when outputting any dynamic value. It doesn’t matter what it is. If it’s a variable, it must be filtered by one of these functions.

This is what a safer version of the view code would look like:

<cfoutput>
<h1>
#linkTo(text=h(post.title), route="post", key=h(post.key()))#
</h1>
<p class="post-meta">
#DateFormat(post.publishedAt)#
</p>
<h2>#NumberFormat(post.commentsCount)# Comments</h2>
</cfoutput>

Now if the data passed into the view isn’t kosher, it’ll either be HTML-escaped or will throw a server error if it trips up DateFormat or NumberFormat. Neither function likes it so much if they don’t receive dates and numbers, respectively. I figure that displaying a server error page as a result of unexpected data types is way better than allowing my app’s users to have their identities stolen.

Notice that even the post.key() is escaped in the call to linkTo. Guess what? A hacker could still even break into your database and change the posts table’s id column to a varchar and insert executable JavaScript into it. Don’t trust anything that is passed through to the view, even if you feel that your database server is as secure as Fort Knox.

Bring back h!

Oh, and what is that h function all about?

Our friend h used to be a view helper in CFWheels v1.1 but was later removed in v1.3.

I usually create an h helper as an alias for EncodeForHtml so I don’t have to type that over and over again in the view. It happens to be the most-used filter that you’ll need, so you’ll thank me later for this tip.

We need to make it easier to escape output so we’re less likely to fall asleep from exhaustion and forget to escape something. I’m considering creating an n helper as an alias for NumberFormat as well for that very reason.

When not to escape output

There are a few examples of when it’s OK to let it all hang out and actually avoid filtering your output:

  • API endpoints
  • Plaintext emails and any email’s subject line
  • Other plaintext formats like CSV
  • Content that is rich text on purpose (while perhaps stripping out script tags if your users can’t be trusted; this is certainly a risk that you need to weigh)

About Chris Peters

With over 20 years of experience, I help plan, execute, and optimize digital experiences.

Leave a comment