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?

10
  • 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, 2014 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). Oct 19, 2014 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, 2014 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. Oct 19, 2014 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, 2014 at 15:56

3 Answers 3

3

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.

(note the following quote is from the original linked page to CORS above http://www.html5rocks.com/en/tutorials/cors/, however, this is now a 404, therefore, the above link has been changed to here.)

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.

Important note: A point was just made in this comment. Sometimes it may also be possible to trigger a simple request with the following Content-Type: text/plain; application/json. Therefore if application/json is checked for in server-side code, then these checks must be robust enough to ensure it is only this content type, or else another more robust method of anti-CSRF checking must be employed (e.g. un-guessable, session specific tokens within the POST body or header).

8
  • -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, 2014 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. Oct 20, 2014 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, 2014 at 15:21
  • 1
    @SilverlightFox Claiming that simple requests can only use one of those three Content-Type values is a dangerous oversimplification. See github.com/mdn/content/pull/11077.
    – jub0bs
    Jul 18, 2022 at 17:24
  • 1
    @jub0bs Many thanks. Post updated to reference your comment. Jul 26, 2022 at 10:26
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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .