4

I have implemented the csrfGenerateToken and csrfVerifyToken functions in trying the prevent a CSRF attack.

I have set up the webpage header with these:

set X-XSS-Protection "1; mode=block"
always set X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
Strict-Transport-Security "max-age=63072000" env=HTTPS

I have done some research and proceed as recommended by Adobe doing something like this:

<cfset tokenVar = 't' & lCase( hash( createUUID() & randRange( 1000, 100000 ), 'MD5', 'UTF-8', 250 ) )>
<form action="updateprofile.cfm" method="post">
    <input type="hidden" name="f#hash( 'tokenVar', 'SHA-256', 'UTF-8')#" value="#tokenVar#">
    <input type="hidden" name="f#hash( 'formToken', 'SHA-256', 'UTF-8')#" value="#csrfGenerateToken( tokenVar, true )#">
    <input type="text" name="emailAddress" value="#EmailAddress#">
    <input type="text" name="phoneNumber" value="#PhoneNumber#">
    <input type="submit" name="btnSubmit" value="Change Profile Info">
</form>

Updateprofile.cfm would have:

<cfparam name="FORM[ 'f' & hash( 'tokenVar', 'SHA-256', 'UTF-8')]" default="0">
<cfparam name="FORM[ 'f' & hash( 'formToken', 'SHA-256', 'UTF-8')]" default="0">
<cfif not csrfVerifyToken( FORM[ 'f' & hash( 'formToken', 'SHA-256', 'UTF-8')], FORM[ 'f' & hash( 'tokenVar', 'SHA-256', 'UTF-8')] )>
    <!--- CSRF attack. Clear cookies and kick user out --->
    <cfinclude template="clearcookies.cfm">
    <cflocation url="signin.htm" addToken = "no">
</cfif>

This will work if 2 accounts are signed in on the same browsers and if one tries to update the other. However, when I simply saved a copy of the resulting html from one of them and save it as csrf-attack.htm locally:

<html><body>
<script>history.pushState('', '', '/')</script>
<form action="http://www.exsample.com/updateprofile.cfm" method="post">
    <input type="hidden" name="f41BE6B4E09CBA69BDB76DBB69B493E8D49F5DD9ED230085913397B4C751D4E60" value="t93315a7c3ecb43d4d1b9422da97ffb09">
    <input type="hidden" name="f08DFC2607D4119D7B16B4C01DC5C00F54B044DC937257ABC411F9A7E55BB4191" value="A0EED67C55F5E17683E2E1B21FF3454FE690E0B1">
    <input type="text" name="emailAddress" value="test@test.com">
    <input type="text" name="phoneNumber" value="1-222-3333">
    <input type="submit" name="btnSubmit" value="Change Profile Info">
</form>
</body><html>

I processed the original form to update the phone number to 1-333-4444. Then I came back to the form again. At this time a new CSRFToken should have been created because ForceNew was set to true.

Then I go to the static HTML page that I have saved, and simply changed the value of the email address to test2@test.com instead of test@test.com with the old token, then clicked the "Change Profile Info" button, I was able to update it to the site!!

Am I doing something wrong or is it how it works? It seems that the token is useless if I can simply copy the token values and manipulate the content, then post it. How can I mitigate issue like this on the receiving page?

Thanks in advance.

Jack
  • 853
  • 1
  • 7
  • 20
  • https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet – Shawn Feb 04 '19 at 07:02
  • To be honest with you, your code looks messy right now. Do you actually need per-request tokens or would a simple session based nonce be sufficient? – Alex Feb 04 '19 at 19:41
  • @Alex maybe you are right. I had to implement this all over and was messy. Perhaps set it once when user login and check it for updates, deletes etc. is sufficient? But I still face the same issue. I am just wondering if I am doing it the right way because of the test and people can still mess with it. I hope CF folks can shed some light on something they are already doing to mitigate this issue. – Jack Feb 04 '19 at 21:18
  • @Alex How do you set it just once? Everywhere I saw, the examples were setting token in hidden form. Even at [owasp.org](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet) it tells people to use the hidden token in form. – Jack Feb 04 '19 at 21:29

2 Answers2

1

The csrfVerifyToken result will still pass until you generate another CSRF token with the same key, at that point it will get invalidated. So if you are making single use tokens then you need to invalidate the old token by calling csrfGenerateToken with the same tokenVar after you call csrfVerifyToken

I wrote up a blog entry with a code example to illustrate this: https://www.petefreitag.com/item/856.cfm

Pete Freitag
  • 1,041
  • 8
  • 8
  • Sorry I didn't include more details. I have updated the issue above. In fact, the form was saved to a static page with the token before pressing the Submit button. Then I made changes to the profile and submitted it on site. I then loop back to the same form, which should have a new token generated already. But I was still able to change the static html page with the old token intact, change a field and submit. The static form still got submitted successfully. – Jack Feb 07 '19 at 04:11
0

Jack, 2 points:

First, things will seem to "not protect anything" if your requests (including that html page) are made from the same browser, thus using the same session cookies for the site, thus using the same cf session.

The generated token is saved in the cf session for that user/browser (saved in a way not visible with a cfdump of the session). And the verify is then checking the passed-in token against that. But if you run the "other" code in another browser, it would NOT share the same session (unless you also copied in the user's cookies).

Second, even if you did duplicate the session cookies, the value in that csrf token field (simulating being grabbed by the bad guy who saw the form and "copied it off" to that other page) will be the value that was created when THEY did that...not the value that a real user would have in their session if they'd been to the page that did the csrfgeneratetoken. So the tokens won't match.

Make sense? It's easy to misconstrue what this is does and does not do, and just as easy to get tripped up setting up a demo prove if it "works", if you don't keep all this in mind.

And I may be lacking in my answer, so am open to comments and criticism.

charlie arehart
  • 6,590
  • 3
  • 27
  • 25