gfrobenius
gfrobenius

Reputation: 4067

ColdFusion Encoding Issue - encodeForHTML and encodeForJavascript Drops Backslashes

To protect against Cross-site Scripting (XSS) I'm using what OWASP recommends, the ESAPI (Enterprise Security API). The esapi.jar file has been included with prior version of ColdFusion but in CF10 you can now easily call some of these helpful functions: encodeForJavascript(), encodeForHTML(), encodeForURL(), encodeForCSS(), and encodeForHTMLAttribute().

I am having troubles with encodeForJavascript(), I'm losing my backslash...

<cfoutput>
    <cfif isDefined("url.name")>
        <!--- Here is the problem, this is identical to the original ascii32to126 string except for one char is missing, the backslash between the brackets ...Z[]... --->
        #url.name#
        <cfabort>
    </cfif>

    <!---
    ASCII 32 thru 126
    !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    In the line below I double up on the double-quotes and pounds in order to get the cfset to work
    --->
    <cfset ascii32to126 = "!""##$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~">

    <script>
        function locateTo(value)
        {
            window.location='thisPage.cfm?name='+encodeURIComponent(value);
            //alert('thisPage.cfm?name='+encodeURIComponent(value));
        }
        locateTo('#encodeForJavaScript(ascii32to126)#');
    </script>
</cfoutput>

I first call encodeForJavaScript() because we are in the JavaScript context.

Then I call encodeURIComponent() to make sure the URL is built properly.

Everything works fine but on the resulting page I have lost my backslash \. What am I missing here?

(Yes, I am aware I also have to protect where I output #url.name#. For this experiment I didn't do that because I needed to view the source to see if the string matched the original string.)

** UPDATE ** - I am running ColdFusion 10 with all the latest patches applied. Problem seems to be in encodeForJavaScript().

Fails with JSStringFormat() also. Doing this shows the backslash is missing for both...

<cfset ascii32to126 = "!""##$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~">
<cfoutput>
    #encodeForHTML(encodeForJavaScript(ascii32to126))#
    <br><br>
    #encodeForHTML(JSStringFormat(ascii32to126))#
</cfoutput>

Upvotes: 1

Views: 3039

Answers (3)

gfrobenius
gfrobenius

Reputation: 4067

(2+ years later) It appears to have since been fixed by Adobe or the ESAPI people. All characters including the backslash are retained. I am now running these versions...

Server Product: ColdFusion

Version: 11,0,09,299201

Tomcat Version: 7.0.68.0

Edition: Developer

Operating System: Windows 10

OS Version: 10.0

Update Level: C:/ColdFusion11/cfusion/lib/updates/chf11000009.jar

Adobe Driver: 5.1.3 (Build 000094)

Java Version: 1.8.0_91

Java Vendor: Oracle Corporation

Upvotes: 0

gfrobenius
gfrobenius

Reputation: 4067

We have our own custom encoding function called ddj()...

<!---
Author: @Brian McGinity
custom version of jsStringFormat/encodeForJavaScript
--->
<cffunction name="ddj" returntype="string" output="false">
    <cfargument name="temp" required="yes" type="string">
    <!---
    <cfset var z = jsStringFormat(arguments.temp)>
    <cfreturn z>
    --->
    <cfset var buf = CreateObject("java", "java.lang.StringBuffer")>
    <cfset var len = Len(arguments.temp)>
    <cfset var char = "">
    <cfset var charcode = 0>
    <cfset var bad   = ' >%}\~];?@&<##{|^[`/:=$+"' & "'">
    <cfset buf.ensureCapacity(JavaCast("int", len+40))>
    <cfif NOT len><cfreturn arguments.temp></cfif>

    <cfloop from="1" to="#len#" index="xmlIdx">
        <cfset char = mid(arguments.temp,xmlIdx,1)>
        <cfset charcode = asc(char)>
        <cfif charcode LT 32 or charcode gt 126>
            <cfset buf.append(JavaCast("string", ""))>
        <cfelse>
            <cfif find(char, bad) gt 0>
                <cfset buf.append(JavaCast("string",  "\u00" & right("0" & formatBaseN(charcode,16),2) ))>
            <cfelse>
                <cfset buf.append(JavaCast("string", char))>
            </cfif>
        </cfif>
    </cfloop>
    <cfreturn buf.toString()>
</cffunction>

Using ddj() I can pass the ascii32to126 string into the function, then do the encodeURIComponent on it, then locate to a page and print it and the string is exactly the same. Seems to work great. But doing it this way I am encoding for JS first, then for the URL second. That doesn't seem right. I've always thought it had to be the other way around (see here). So I'm trying to figure out if I can break it. Here is the test case that appears to work...

<cfoutput>
    <cfif isDefined("url.name")>
        #url.name#
        <cfabort>
    </cfif>

    <!--- !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ --->
    <cfset ascii32to126 = "!""##$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~">

    <script>
        function goToUrl(value)
        {
            window.location='thisPage.cfm?name='+encodeURIComponent(value);
            //alert('thisPage.cfm?name='+encodeURIComponent(value));
        }
        goToUrl('#ddj(ascii32to126)#');
    </script>
</cfoutput>

Upvotes: 0

Adrian J. Moreno
Adrian J. Moreno

Reputation: 14859

FWIW, we have been using all of the encodeForX functions for over a year now and only have problems when a developer uses the wrong context. We have strictly outlawed the use of HTMLEditFormat and have a Jenkins server check for it (among other outlawed functions & tags) as builds are run throughout the day.

You are encoding the string for JavaScript and then encoding it for URL. I believe you should be encoding for URL first, then encoding for JavaScript. There don't seem to be any lost characters when I compare the output to the un-encoded string.


<cfoutput>
    <cfif isDefined("url.name")>
        <!--- Here is the problem, this is identical to the original ascii32to126 string except for one char is missing, the backslash between the brackets ...Z[]... --->
        #url.name#
        <cfabort>
    </cfif>

    <!---
    ASCII 32 thru 126
    !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    In the line below I double up on the double-quotes and pounds in order to get the cfset to work
    --->
    <!--- Using Chr() to bypass character escaping. --->
    <cfset ascii32to126 = "!#chr(34)##chr(35)#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~">
    <cfdump var="#ascii32to126#" />

    <script>
        function locateTo(a, b) {
            console.log(a); // 1. JavaScript Encoded.
            console.log(b); // 2. URL encoded, then JavaScript encoded.
            console.log(decodeURIComponent(b));// 3. Matches JavaScript encoded.
            console.log( 'thisPage.cfm?name=' + b ); // 4. Correct string.
        }

        locateTo('#encodeForJavaScript(ascii32to126)#', '#encodeForJavaScript(encodeForURL(ascii32to126))#');
    </script>
</cfoutput>

Console Output


!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 

%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E 

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 

thisPage.cfm?name=%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E 

Upvotes: 1

Related Questions