Simulating a CSRF Attack Part 2
By: David Klempfner
Tags: Csrf, Security, Software Development, Dotnet Core, Programming

Simulating a CSRF Attack Part 2

Make sure you’ve read part 1 if you haven’t already.

We’re now going to look at the difference between cross-domain GET/POST and PUT/DELETE requests.

POST Request

To test out what happens when you make a POST request, update Index.cshtml in FakeBank with this code (line 8 and 9 have changed):

Refresh the FakeBank webpage and you’ll notice the Withdraw() action method in GoodBank gets executed.

Look back in the F12 Dev Tools in the FakeBank webpage and you’ll see it’s the same as the GET request. The action method gets executed, but you can’t view the response.

It doesn’t matter that the hacker can’t see the response because he has just sent himself $85 from your account!

PUT/DELETE Requests

Update Index.cshtml in FakeBank with this code:

This will attempt to update the victim’s email address at GoodBank with the hacker’s email address.

Refresh the FakeBank webpage and you’ll notice the UpdateEmailAddress() action method’s breakpoint is not even hit.

Preflight Request

The GET/POST methods were executed, why is it different with a PUT (and DELETE) request?

Have a look at the F12 DevTools Network tab in the FakeBank webpage:

You can see two requests.

The first one is the original request which does not complete.

The second request is an OPTIONS request which asks the web server if it will accept a PUT request (or a DELETE request if you used DELETE instead).

CORS

This happens because of CORS (Cross Origin Resource Sharing).

I won’t go into much detail about CORS, there are plenty of good articles for that. But basically, it’s a way of allowing a web browser to make a cross-origin request.

In the early days, a browser’s Same-Origin policy would prevent Javascript from displaying the response from a GET/POST request, and it still does this today if the web server doesn’t respond with the right CORS headers to allow the request.

It would also completely block a PUT/DELETE request. In fact, many old web servers which are unaware of CORS would never expect to receive a cross-origin PUT/DELETE request in the first place.

“Being nice” to old web servers

The OPTIONS request is referred to as a preflight request.

This is about not breaking the rules. Old web servers do not expect to receive cross-origin PUT/DELETE requests, and to respect this, a “preflight” check is done to see if the web server is “CORS aware”.

If it’s aware of CORS, it will respond with the right headers to inform the caller that it is aware of CORS, and it will simply accept or decline the real request based on the Origin header.

The creators of modern day web browsers want to prevent their users from being exploited. Without the preflight check, the PUT/DELETE requests would be sent and would be executed.

Why is PUT/DELETE treated differently to GET/POST?

How things were from the beginning

The first thing to understand is that since the beginning, GET and POST requests were, and still are, the most common requests.

Users will regularly click on links, and type in URLs into their browser, which all make GET requests.

Users also make POST requests when filling out forms and clicking the “Submit” button. These submitted forms can only make POST/GET requests. The browser does not support other HTTP verbs in a form.

These requests are often cross-origin requests, and have always been allowed, also from Javascript.

A POST request using Javascript has always been exploitable via CSRF, and the reason this did not stop browsers from preventing it was because there are many use cases where a cross-origin request is desired. It would be up to the developers to implement security such as special tokens, to prevent CSRF attacks with POST requests.

PUT/DELETE requests are from Javascript

On the other hand, PUT/DELETE requests are different because they are only possible in a browswer via Javascript.

What about a POST request with json as the content type?

It’s actually not just PUT/DELETE that are preflighted. It’s any request that could only have been sent via Javascript.

If you send a POST request by clicking a submit button for a form, the Content-Type header will be application/x-www-form-urlencoded . If a POST request was sent via Javascript and the Content-Type header was changed to something else, then that request would also be preflighted since it could only have been sent from Javascript.

Safe/Unsafe

A cross-origin PUT/DELETE request would most likely be malicious, since they can execute in the background when a page loads.

CORS is about making things less secure and maintaining backwards compatibility.

It’s for this reason, that PUT/DELETE are preflighted rather than just allowing the request to be sent to a vulnerable server.

GET/POST are not blocked because these were possible from a browser even before CORS existed.