4

I'm currently trying to wrap my head around how to prevent CSRF.

My first solution was to use a token which is suggested everywhere. That would of course fix this problem:

<img src="http://api.example.com/me/delete">

But what I can't get my head around is that.

GET http://api.example.com/me/delete

Will indeed fail because a valid token didn't come along with that request, but what prevents an attacker from doing the above and instead just do the following

GET http://api.example.com/me/token/generate
// parse the response
GET http://api.example.com/me/delete?token=...

Valid token was passed along the request, my user is now deleted...


My REST API allows any request to come through.

header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);

The reason I "need" that is that, I'm not only using the REST API on the domain itself through forms and JavaScript. But I'm doing requests using cURL in C++.

If tokens in this case aren't a "safe" solution, then what would/could a safer/better solution be?

Further what does something like Spotify do, while they both have their "Web API" and the music software itself?


Also and I know this is far fetched, but is it possible to allow anything access to my API but have a per origin authentication?

Thereby that if http://example.com is "logged into" the API, it doesn't mean that http://some-other-website.com is (or further when requesting using cURL).

I know this is far fetched, as the "solution" would be to check the origin header, but that can be spoofed.

  • 2
    I don't really think you need CSRF protection for you API. I do think you need authentication. Take a look at OAuth. I think that's what you need: oauth.net – Tijme Jun 8 '15 at 15:48
  • CSRF is only a problem when you have a client that automatically sends data like session cookies for any request to a site. So, as @Rabobank said, no need for CSRF protection in API as the API client doesn't have access to the browsers cookies. – Neil Smithline Jun 8 '15 at 17:23
  • @NeilSmithline so each time a request is made to the API, authentication needs to be made? So at each request send login data along? How would you safely "do" that? Couldn't an attacker then GET the page and read the "login data" from the page? – Vallentin Jun 8 '15 at 23:00
  • OAUTH is a token authentication system (brief summary of token authn) which uses public keys and digital signature to validate authentication without the need to pass uname/pwd on each request. The API client would authenticate to get a token and then pass the token on subsequent calls. The server validates the digital signature of the token before proceeding. – Neil Smithline Jun 8 '15 at 23:21
  • Ahh - found a much better reference. It talks about CORS and CSRF specifically. See scotch.io/tutorials/… – Neil Smithline Jun 8 '15 at 23:25
7

The Same Origin Policy would normally prevent your scenario from taking place:

GET http://api.example.com/me/token/generate
// parse the response

The response cannot be parsed through a third party domain making requests to your domain. The browser will normally block it due to the SOP.

I say normally because you have also added the following response header to relax the Same Origin rules:

header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);

Under the following misconception:

The reason I "need" that is that, I'm not only using the REST API on the domain itself through forms and JavaScript. But I'm doing requests using cURL in C++.

This header does not have any bearing on cURL. The header is parsed by browsers only, implementing the SOP. It does nothing to prevent direct requests coming for a web client.

Solution

On your api.example.com domain, only allow the front-end to make requests through it using the user's browser:

Access-Control-Allow-Origin: example.com

On your example.com site, send the following header with all API AJAX requests:

X-Requested-With: XMLHttpRequest

See this answer for info on this. In a nutshell, this header cannot be sent cross domain without CORS being enabled.

Verify on your API backend that this header is present when a request is received. If it is not then it is an CSRF attack.

On your server-side requests using cURL, simply add the header manually. e.g.

X-Requested-With: cURL

As you're verifying that it is present, these requests will succeed.

Finally, add some authentication using cookies. Your logged in website users should have an authentication ticket that can be sent in a cookie, and you should have one for your backend cURL requests to use. This will ensure that nobody can use your API without permission. You may want to restrict the actions that web users can make using authorisation levels because otherwise they may try to maliciously execute a function that only your cURL server tool should do.

1

I hope i understood your question.

As for the first part, Normally, as the attacker will be on a different origin than your server, so the browser wont allow it to read the response thereby preventing it from 'parsing the response' and sending the token in a subsequent request. But you have explicitly allowed all origins which renders this protection futile.

Also, the token generated should obviously require authentication of some sort, wherein User A's token wont be valid for User B so even if he manually sends the token, it will be invalid.

Second part, If you had any sort of a properly implemented token mechanism, no matter where the request came from, the token would prevent malicious activity.

As for the third part, if i understood your question correctly: As far as working from a browser goes, CORS doesn't allow setting the origin using javascript or any client-side code. So, in the case of a CSRF attack requiring a certain value for Origin, the attacker cant spoof that value using Javascript.

Hope that helps.

  • It will allow it to read the response since the Access-Control-Allow-Origin: header is set. – SilverlightFox Jun 8 '15 at 16:38
  • Shouldn't the requesting resource's origin match the one set for Access-Control-Allow-Origin for it to read the resource? – Aatif Shahdad Jun 8 '15 at 16:56
  • Yes, but that was what the posted code was doing: header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]); – SilverlightFox Jun 8 '15 at 16:59
  • Ah, you are right. I misread the second question. Thank you. – Aatif Shahdad Jun 8 '15 at 17:09
  • 1
    @Vallentin To me it seems that you would need to read the response to get the token, which also u seem to allow with access-control headers. If u had any sort of cookie based authentication, that would help as u cannot set access-control to a * with 'allow credentials' set to true. – Aatif Shahdad Aug 26 '15 at 12:54
1

Looking at the scenario from the original question, I think the OAuth/JWT token is the recommended approach. For those scenarios, you want to allow other origins to have access to your APIs, but you only allow those requests that are authorized. This is where a JWT token provides this level of security as claims are added to the token and you can add the authorization filters based on those claims.

For those looking to only allow same origin requests, no user authentication and prevent CSRF, an anti-forgery token with a validation filter can do the trick. A typical case could be a contact form. For more information on this, take a look at this example with an angularjs app:

http://www.ozkary.com/2016/03/web-api-anti-forgery-token-angularjs.html

hope it helps.

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.