Turning Off "InvalidTag" ScriptProtect Safely In ColdFusion 2021
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 Smith
InvalidTag
alert(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()
andencodeForHtmlAttribute()
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 appropriatenonce
attribute at runtime. Remember, thenonce
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
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.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
orfalse
are valid values.Instead, the valid values are instead
all
ornone
, 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 ofgetapplicationmetadata().scriptprotect
will show, which will return insteadnone
(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.
@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 usetrue
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.
I didn't know about the scopes feature of scriptprotect. That is very cool! Just so people know, you can pass a comma separated list of scopes you want to protect, like this:
this.scriptProtect="url,cookie,cgi";
The above code would protect the URL, Cookie, and CGI scopes, but not the Form scope.
Docs: https://cfdocs.org/cfapplication
@Ben, regarding your article, I too have felt pain with scriptprotect enabled and wanting to allow users to insert things like iframe content via a content management system form. I'm actually surprised Adobe (and Lucee) haven't given us a way to disable script protection for specific (trusted) requests, which feels like a no-brainer, in my opinion.
Also, I didn't know about the
nonce
property before reading your post. Thank you, I am going to look into it more.Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →