3

I'm performing an authorized vulnerability analysis on a custom web service and have discovered a CSRF vulnerability.

Due to there not being form tokens coupled with the service not checking for the origin header I believed I could forge requests via AJAX POSTs from an "infected" proof of concept page.

Due to the way the web server maintains state it requires a series of six sequential POSTs to complete my attack. Here comes the problem:

My first PoC involved creating six simple html pages (each with one form) that I would load and submit one by one, in order, to validate my assumptions. This worked fine so I converted these form actions to sequential AJAX POSTs. My attack stopped working at this point. I compared the actual HTTP requests for both content and order - all of the application layer data matches.

I became suspicious of the same origin policy for some reason despite NOT needing to read any return data so I started Chrome with the --disable-web-security option (to disable the policy), tried my attack again, and this time it succeeded. It seems the same origin policy is somehow preventing my attack, but I have no idea why since I don't need to read any returned data. What am I not understanding? What have I not thought about? Is there any way to accomplish what I want to do?

  • You can't use an AJAX post request for a CSRF exploit. You need to use <form> tags or <img> tags. Use onload events to coodiate the order of requests sent. – rook Oct 17 '14 at 3:39
  • @Rook: How do you mean can't? Surely this depends on the implementation of the POST handler on the website (e.g. it is not checking Origin or a custom header such as X-Requested-With). – SilverlightFox Oct 19 '14 at 9:18
  • @SilverlightFox I should reiterate, XHR can never be used in a CSRF attack because the CORS pre-flight request prevents this attack. – rook Oct 19 '14 at 15:48
  • @rook What if the request is a "simple" request that doesn't require a preflight? If the site isn't checking for extra headers then this would go straight through. – SilverlightFox Oct 19 '14 at 15:53
  • @SilverlightFox I'm sorry, but it just doesn't work that way. Prove me wrong, show me a CSRF exploit that you have written that uses an XHR. (This is an impossible task for a modern browser.) If the target has CORS enabled, the the web app has bigger problems than CSRF. – rook Oct 19 '14 at 15:56
2

There is no reason that this should not work as long as third party cookies are enabled in your browser.

HTML form POSTs can be used, or in the case that the forms are a multi-stage process then this will be more useful to run as XHR requests as your attacking page can then control the requests and issue them in turn - just as your are doing.

Make sure that you are setting withCredentials on your AJAX requests.

In jQuery this is done like the following

$.ajax({
  type: "POST",
    url: 'http://www.example.com/Controller/Action',
    data: 'foo=bar',
    async: true,
    xhrFields: {
      withCredentials: true
   }
});

Other answers suggest that using withCredentials means that a CORS pre-flight request is sent to the resource instead of the intended GET or POST. This is not the case as long as the request is a "simple request". If your exploit worked with HTML forms then the correct AJAX equivalent would be a simple request, so check that all you code is correct.

Cross-origin requests come in two flavors:

  • simple requests
  • "not-so-simple requests" (a term I just made up)

Simple requests are requests that meet the following criteria:

  • HTTP Method matches (case-sensitive) one of:

    • HEAD
    • GET
    • POST
  • HTTP Headers matches (case-insensitive):
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type, but only if the value is one of:
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain

Simple requests are characterized as such because they can already be made from a browser without using CORS.

This is actually mentioned in the CORS specification and indicates that CORS does not actually add security, it only enables cross-domain communication when both the client and server opt into the specification. If both parties do not opt in (such as in a CSRF attack where the victim site does not wish to be communicated to in this fashion), then the browser will act in a similar way to pre-CORS. i.e. cookies will be sent although the response will not be readable:

resources conforming to this specification must always be prepared to expect simple cross-origin requests with credentials. Because of this, resources for which simple requests have significance other than retrieval must protect themselves from Cross-Site Request Forgery (CSRF) by requiring the inclusion of an unguessable token in the explicitly provided content of the request.

Back to your question:

I compared the actual HTTP requests for both content and order - all of the application layer data matches.

I suggest that you use an intercepting proxy such as Burp or Fiddler2 to verify the requests. Checking the requests from inside the browser is not an accurate way of verifying this because the requests viewed in Chrome are subject to be interpreted from the Origin of the page they are issued from and if the Origin doesn't see the response due to the Same Origin Policy, then neither will you in dev tools. Chrome informs you of this via the “CAUTION: provisional headers are shown” message.

For example, the following is the same request as shown in Chrome dev tools and then in Burp. Notice how only the external tool shows the cookies.

Requests

I've tested this behaviour with Chrome 38, Firefox 32 and Internet Explorer 11 on Windows 7 x64, and it is consistent across browsers (however, make sure 3rd party cookies are enabled in settings) so this is further indication that this behaviour complies to current standards.

So my advice is to double check your code and requests using an intercepting proxy. Maybe post them on here for another pair of eyes to check as if it is working one way but not the other then there must be a mistake somewhere because the requests will not be the same.

Also, make sure that your code isn't exiting early due to the error returned because of Same Origin Policy violation.

  • -1 I don't believe the OPs question anything to do with a strange, non-standards complaint browser, and as far as I can tell this answer nothing to do with exploitation of CSRF. I would take away the -1, but I can't. – rook Oct 19 '14 at 19:02
  • @Rook: I've clarified my answer. I admit my original didn't quite make the point I wanted it to (by a long way). As now mentioned this has been tested from "standards compliant browsers" and is even mentioned in the CORS spec that without care taken on the server side implementation XHR can be used to initiate CSRF. – SilverlightFox Oct 20 '14 at 12:55
  • 1
    +1 this post is so good I deleted mine. Also your right, the ajax method does send cookies... but cookies are only sometimes displayed in the browser's network view. BURP saw the cookies, and BURP's XHR CSRF builder is a good method of delivering CSRF exploits. Thank you for clarifying this for me. – rook Oct 20 '14 at 15:21
  • withCredentials was the key to my problem. The cookies weren't being sent in my original attack. After I added that directive I verified the authentication cookies were sent! For your information, after my own research, what you have coined "not-so-simple requests" are called "Preflighted Requests" =P – Ryan Oct 21 '14 at 0:09
  • ...and for anyone who was wondering why this was working after I disabled the same origin policy (despite withCredentials being set to false), this web service uses Windows integrated NTLM authentication. When I disabled the cross-origin policy restrictions my browser could actually read the server challenge and respond with proper credentials during the challenge response, "invisible" to me. With the same-origin policy enabled my browser could not read the response and hence, could not respond to the challenge. – Ryan Oct 21 '14 at 0:15
0

Try catching the error with nothing. The SOP error does raise an exception, which does stop your script execution. If you instead Catch the error, the request will be fired away, your response will be filtered, a exception is thrown that will catched, and your script will continue with the next request.

So basically, now it works like this:

attackstep1() << script stop here due to a exception, thus only step1 of attack is performed
attackstep2()
attackstep3()
.....

Instead, try something like this:

try {
attackstep1()
}
Catch(e) {}
try {
attackstep2()
}
Catch(e) {}
try {
attackstep3()
}
Catch(e) {}
try {
....
}
Catch(e) {}

This will ignore the SOP permission error and continue script execution. Note that you will not get access to any Resources that the protected resource returns.

0

The error message you got is because the Cross-origin resource sharing (CORS) is not enabled on the server you try to attack. So all your AJAX requests will not be accepted by the server that host the web services.

So if you enable CORS on your target server, the server will accept your AJAX requests. Do not use AJAX since if CORS is not enabled your request will be denied. You can use curl a nice command line tools that can call web services.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.