The other day, I wrote an article about dynamically generating
<script> tags using Umbrella JS. Historically, writing about the
<script> tag has been somewhat challenging - from a technical standpoint - because the ColdFusion server goes out of its way to protect You from persisted Cross-Site Scripting (XSS) attacks. It does this by scanning input scopes (ex, url, form, cgi, cookie) and replacing suspicious tag names (ex, script, object, embed, applet, iframe) with the phrase "InvalidTag". I was able to turn this behavior off using the
this.scriptProtect="none". This feels like a scary step, however; so, I wanted to just think out loud about why this is safe to do in my particular context.
this.scriptProtect exists in the first place is to prevent a bad actor from submitting content that contains malicious code which will, at some point in the future, be rendered in the browser and expose some sort of vulnerability on another user's system. Imagine that this bad actor was filling out an identification form and provided their name as:
Now, imagine that this content were subsequently rendered in HTML using no safe-guards:
<p> #user.name# </p>
When this page is rendered, the
<script> tag were doing something much more nefarious, like making an API call to change the user's security settings.
To help prevent this, the default behavior of ColdFusion is to intercept that form submission and replace the aforementioned Name input value with:
This way, the
alert(1) becomes inert text.
This is a nice gesture; but, as I mentioned above, this makes it very hard for a blog on web development to have a conversation about anything related to
So, I went into my
Application.cfc ColdFusion application framework component and disabled the ScriptProtect feature:
this.scriptProtect = true;
And, here's why I think that this won't expose a security risk on my blog:
I am very good about escaping user-generated content using
encodeForHtmlAttribute()as much as I possibly can. As such, any "simple values" that contain malicious code should be escaped on output.
I am using the OWASP AntiSamy project to sanitize HTML input. Once a user's Markdown comments are converted into HTML using Flexmark, I then run the resultant HTML through a strict AntiSamy policy. This policy is a very narrow allow-list of tags and attributes which should prevent any embedded script, object, applet, iframe, etc. tags from making their way through input validation.
I have a strict Content Security Policy (CSP) in place. This CSP requires the injection of a
nonceattribute on all script, object, and media tags. Which means that any inline
<script>tag that a user somehow manages to inject will subsequently get blocked (from execution) by modern browsers since it won't have the appropriate
nonceattribute at runtime. Remember, the
noncevalue is uniquely generated on every single request.
ScriptProtectsetting only sanitizes value on input. Which means that any malicious code that has already been persisted to the database is still dangerous. As such, I have to secure all my output regardless of this application setting.
No one security measure is enough to keep a web application secure. The only hope that we have is that when we take enough small steps, we can manage to stay ahead of the malicious actors in the ongoing "security arms race". And, in my context - for this Adobe ColdFusion 2021 blog - I feel that I have sufficient protections in place that I can get rid of the scriptProtect setting and allow users to submit markdown content that contains references to
Epilogue on Null Byte Injection Protection
If you read the documentation on the
Application.cfc settings, you'll see that Adobe ColdFusion takes one additional step in protection for scriptProtect: it replaces null bytes with spaces:
Enabling the global site protection replaces all the null bytes (%00) with an %20 (space). This is to prevent Null Byte injection Attacks as part of the Protection.
Apparently, the null byte injection will cause early String termination which may allow a malicious input String to pass validation and then behave differently when being consumed by the application logic (such as during file operations). I was curious to see how this was being implemented, so I went to look at Lucee CFML's open source implementation of ScriptProtect. But, I don't see anything in there version about null byte replacement. It could be somewhere else in the request processing; or, it may just not be there, period.
As one final step, I tried to trigger this behavior myself. I created two text files:
echo "I am a TXT file" >> data.txt echo "I am a MD file" >> data.txt.md
And then, I tried to see if I could use a null byte to cause the wrong file to be read:
<cfscript> writeDump( fileRead( expandPath( "./" & urlDecode( "data.txt%00.md" ) ) ) ); </cfscript>
The test here is to see if the URL-decoded
%00 will cause early termination of the file-path, causing the
.txt file to be read-in. But, instead of getting the
.txt file content, I just get a "File Not Found" error. This behavior happens in both my local Unix Docker container as well as in my production Windows VPS. As such, I'm not quite sure how to actually trigger this malicious pathway.
Could it be that this is actually a hold-over from when ColdFusion was written on top of C? Maybe it's no longer relevant now that it's built on top of Java (see Stack Exchange thread)? I have no idea - I'm just speculating.
Want to use code from this post? Check out the license.
And, here's a test comment to demonstrate that I can include
<script> tags in my code blocks (though, they are rejected by AntiSamy if I try to include them as raw script tags.