Skip to main content
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Emily Christiansen
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Emily Christiansen ( @egchristiansen )

Turning Off "InvalidTag" ScriptProtect Safely In ColdFusion 2021

By
Published in Comments (3)

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 Application.cfc setting, 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.

The reason 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:

Jo-Hanna Smith<script>alert(1)</script>

Now, imagine that this content were subsequently rendered in HTML using no safe-guards:

<p> #user.name# </p>

When this page is rendered, the alert(1) would execute in the JavaScript runtime. Now imagine that this <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:

Jo-Hanna SmithInvalidTagalert(1)</script>

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 <script> tags.

So, I went into my Application.cfc ColdFusion application framework component and disabled the ScriptProtect feature:

this.scriptProtect = "none";

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 encodeForHtml() and 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 nonce attribute 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 nonce attribute at runtime. Remember, the nonce value is uniquely generated on every single request.

  • The ScriptProtect setting 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 <script> tags.

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.

Reader Comments

15,748 Comments

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.

50 Comments

Hey, Ben. I notice in your post you say that you "disabled the ScriptProtect feature" by setting this.scriptProtect = true. I'm curious: wouldn't you think that would ENABLE it? And that setting it to FALSE should disable it? :-)

More than that, I think I can explain why setting it to "true" DID disable it, but there's a more grave security aspect of this, I think: it turns out the docs (the Adobe CFML Reference for this.scriptprotect) are mistaken in saying that true or false are valid values.

Instead, the valid values are instead all or none, or any of the 4 scopes that it can monitor (form, url, cgi, cookie), regardless of which way one sets such an app-level scriptprotect.

Some good news is that the CFML Reference for cfapplication gets this right, as do the cfdocs.org pages (on either approach).

The bad news in all this is that it's a security risk that someone might set it to true expecting to enable it, when instead it disables it (as you found), and as a dump of getapplicationmetadata().scriptprotect will show, which will return instead none (as it does if you use ANY invalid value--yikes!). I'm referring to ACF here.

And in fact, I just wrote an Adobe tracker ticket on this: https://tracker.adobe.com/#/view/CF-4220647, though it's possible that Adobe may make it hidden soon when they see it's got a security aspect. I may do a blog post on the matter, in that case. Developers need to understand this (and I don't see how publicizing the info "makes CF insecure").

As for Lucee, I couldn't get a demo working on trycf.com at the moment, as it's being unresponsive. I do have a cffiddle gist with cfapplication and a dump of that method here, showing how "true" results in "none".

Anyway, I came across your post here while looking into the matter, and I thought I'd share the news for you or other readers.

15,748 Comments

@Charlie,

Great catch 😨 that was just a typo on my part. I have no idea what happened. I actually refer to "none" in the intro paragraph; but then use true later on. I just double-checked my actual CFML and I'm using "none" in my production code.

Thank you for spotting that - I've updated the post. That was quite misleading.

Re: scopes, I didn't realize you could lock the behavior down to a given scope though - that's really cool.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel