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> |
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
(orHtmlEditFormat
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)