Is CSRF safe at all? - csrf

Lot of people talk about implementing CSRF to stop cross site attacks on a webpage. But i think it is pretty easy to compromise CSRF and make requests to a server.
So how it works ?
You start with a page, render a form and keep a hidden field with CSRF token.
When form is submitted the other page will expect CSRF token in order to validate the request.
While validating the request, server will check CSRF token with a secret to see if token is valid.
Now the secret itself is stored in session or cookie.
Which means if i go to a website, copy the CSRF token from the webpage, and session value from browser network tab. After this i can construct a CURL request, set these values and make as many requests as i can.
So what is the need of CSRF at all ?

The scenario you define is not CSRF.
Your scenario
Alice goes to Bob's website. Alice gets a cookie from Bob's website. Alice uses that data to make a request that she designed to Bob's website.
This is fine. Everything going on here is between Alice and Bob.
If Bob can't trust Alice to send good data, then that is a different problem entirely.
CSRF scenario
Alice goes to Bob's website. Alice gets a cookie from Bob's website. Alice goes to Mallery's website. Mallery's website triggers a form submission to Bob's website. Bob accepts the data because it came from Alice's browser with Alice's cookie.
i.e. Mallery's site is forging a request from Alice to Bob's site.
The anti-CSRF token is designed to prevent Mallery from making the request through Alice's browser (because Mallery can't get the token to put in the form data, so can't match the token in the cookie).

Related

How to reject http/https request if not coming from allowed website or source

I have an api service: my-api.com/getData
My api expects an api-key from customer to authenticate the request.
My customer's front-end hosted on: customer.com
My customer's back-end hosted on: customer-backend.com
There is no Log-in option in customer's website
Scenario
In customer's website, a button sends request to customer-backend.com/use-my-api
This request executes post('my-api.com/getData', api-key) in backend
So visitor gets data and visitor is not able to see api-key of my customer.
Problem
When a smart third guy uses post('customer-backend.com/use-my-api')
in his own website or back-end. He gets data without providing api-key
This is a very unwanted situation for my customer
Question
How can we reject all requests from outside of the allowed website or source. Changing the architecture of service is not a problem for me and my customer.
As far as i know checking request headers for host is not a reliable. Since it can be changed by sender easily.
An HTTP request does not come "from a website", but it comes from an HTTP client, for example, a browser that is visiting the website. In order to prevent unauthorized requests, your customer's backend must issue a "secret" to the HTTP client, which the client must then include in the request.
A common technique to achieve this is the following:
Visitors must log-in to your customer's website.
After successful logon, your customer's backend sends a session cookie to the visitor's browser (this cookie is the "secret", because it is visible only to the visitor and cannot be guessed by "smart third guys").
The request which the visitor makes by pressing a button includes the session cookie.
When your customer's backend (customer-backend.com/use-my-api) receives a request, it validates the session cookie before making the post('my-api.com/getData', api-key).
Without a log-in mechanism or something equivalent, your customer's website is public and can be (mis-)used by anyone, even if that leads to (paid) API requests made with the customer's API key.

Facebook OAuth security using passport-facebook

I am currently using a client-side React component to have a user login to Facebook via OAuth in my application. On the server-side, I use the npm package passport-facebook-token to validate the authenticity of the accessToken after a successful client-side login.
One practice I do not see often is in addition to asking Facebook if the accessToken is valid, shouldn't the server also check if the email provided by the client's payload matches the e-mail coming back from Facebook? Allow me to use defined client/server technologies to illustrate my question:
1) User uses React component on the client to authenticate with Facebook.
2) React component successfully authenticates with Facebook and fires an HTTP request to the server with an access token and the user's email.
3) The server, running Node.JS and passport-facebook, now needs to verify the authenticity of the access token directly from Facebook. Facebook does not care for an e-mail. It will just verify the access token.
4) Facebook returns a response to Node.js confirming the authenticity of the access token. The response also contains other metadata about the user, including their email and other profile data.
My question is, should Node.js take the email that's also coming back from Facebook's access token verification payload, and verify that it is what came back from the React client? Would this not prevent someone from brute-forcing an accessToken and require them to not only have an accessToken but also know who the accessToken belongs to? This could prevent a user from submitting a bunch of HTTP POST requests to the Node.js server attempting different access tokens. They would not only have to guess an access token assigned to the application's clientID, but also know the e-mail it belongs to. Is this an over-engineered approach?
Really the best way I can think of to make your OAuth accessToken and 'code' value less prone to brute-forcing is using a Cryptographic Number Generator to create a 128-bit length string of random data and encoding it with base 64 to use as your code. It's extremely unlikely that it would be guessed by a computer or by someone redirecting to and from the authorization endpoint and the redirect-uri with query parameters.
Another method of fortification is limiting the rate of authorizations by IP address (which you can do instead of email through Node.js) but that is usually not a problem for most well-equipped hackers. I highly advise the first method for creating a more secure service.
Your approach to validate the email as well as the token is a bit superfluous because Facebook's opaque user access tokens are inherently tied to email.
From Facebook
An access token is an opaque string that identifies a user, app, or Page
"opaque" is defined by Auth0 here
Opaque Access Tokens are tokens in a proprietary format that typically contain some identifier to information in a server’s persistent storage
In your case, the identifier is the user's email, and the server belongs to Facebook.
I will elaborate further. Here is your step by step with some edits:
User uses React component on the client to authenticate with Facebook, inputting both their email and password directly to Facebook. React component gets the token from Facebook on login success.
React component successfully authenticates with Facebook and fires an HTTP request to the server with an access token and the user's email.
The server, running Node.JS and passport-facebook, now needs to verify the authenticity of the access token directly from Facebook. Facebook does not care for an e-mail. It will just verify the access token because the access token is already tied to the email.
Facebook returns a response to Node.js confirming the authenticity of the access token. The response also contains other metadata about the user, including their email and other profile data.
This is Facebook's bug bounty program. If their OAuth was really as cracked as to require a second email validation, it would have been patched almost immediately by this incentive.

Do I need CSRF protection for /login endpoint?

I know
this question has already been asked so many times, but after hours of searching I still don't have a clear answer to my problem.
Even projects like https://github.com/pillarjs/understanding-csrf have been abandoned and have not answered to new questions and doubts over the years like this.
PROBLEM
Let's say I have:
a back-end on back.domain.com and
a front-end on front.domain.com.
My back-end is a simply nodejs app with these rest endpoints:
POST /login:
accepts JSON body like: {"username": "myname", "password": "mypass"}
verify credentials
if OK gives 200 and create a cookie with session
if NOT gives 401
GET /players:
check session in cookie
if OK gives 200 with {"players": "[...]"}
if NOT gives 401
POST /player/1:
check session in cookie
if OK gives 200 and edit player
if NOT gives 401
My front-end app has:
/login page with a form (with username and password fields) for issue a POST request to back.domain.com/login
/players which request a GET request to back.domain.com/players
a button which issues a POST request to back.domain.com/player/1
QUESTIONS
Do I need CSRF protection in this scenario?
I think YES, I need because an attacker can issue a request to back.domain.com/player/1 from malicious.site.com and use my session cookie to edit player because I'm logged in (and I still have a session cookie) on my domain.com.
Do I need CSRF protection (e.g. an X-CSRF-Token header) when I the first time login on back.domain.com/login?
In this scenario I still don't have any session cookie in my browser.
And also I don't know where to get my CSRF token for X-CSRF-Token authorization header too.
I read on https://fractalideas.com/blog/making-react-and-django-play-well-together-single-page-app-model they are creating a dedicated endpoint on back-end for this and they explain it's not a security vulnerability.
What do you think about?
You are correct.
You DO need CSRF protection any time BOTH of the following are true:
the browser is automatically providing the authentication mechanism (most common way this is done is with a cookie)
the operation is state-changing on your backend
Of your three endpoints, only one meets both of those conditions.
GET /players/: get is not a state-changing operation. No CSRF protection needed.
POST /player/1/: authentication provided by cookie; post is state-changing. Needs CSRF protection!
POST /login/: the browser is not automatically providing the authentication information; it’s coming from data the user has intentionally typed in and submitted. No CSRF protection needed.
You’ll find other schools of thought - this other stack overflow post indicates the possibility of privacy-violation attacks, but the method described stretches credulity a bit in my opinion. And in any case you are right - if your frontend and backend are being served by totally different servers, your frontend won’t have the CSRF token before the user logs in.

CSRF token timeout

This page describes a use case to explain CSRF attacks (16.1):
https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html
But if the user is indeed logged in the bank's website, then isn't it possible for the evil website to make a GET request to get a fresh CSRF token, and craft a POST without needing the user at all?
The answer must be no, otherwise CSRF token would be useless, but I don't understand why?
The answer is "no" and the reason is Same-Origin Policy.
SOP means that a page from evil.com cannot read any response to requests that it may send to example.com. Most direct means to send a request will be blocked by the browser (SOP), but there are many workarounds. For example, evil.com could send
GET requests by imbedding an <img>, <script>, <css> and setting src="http://example.com/path" (or <a href="http://example.com/path">).
POST requests by submitting a form.
Since evil.com cannot read any of the responses, it cannot read the CSRF token.

What is a CSRF token? What is its importance and how does it work?

I am writing an application (Django, it so happens) and I just want an idea of what actually a "CSRF token" is and how it protects the data.
Is the post data not safe if you do not use CSRF tokens?
Cross-Site Request Forgery (CSRF) in simple words
Assume you are currently logged into your online banking at www.mybank.com
Assume a money transfer from mybank.com will result in a request of (conceptually) the form http://www.mybank.com/transfer?to=<SomeAccountnumber>;amount=<SomeAmount>. (Your account number is not needed, because it is implied by your login.)
You visit www.cute-cat-pictures.org, not knowing that it is a malicious site.
If the owner of that site knows the form of the above request (easy!) and correctly guesses you are logged into mybank.com (requires some luck!), they could include on their page a request like http://www.mybank.com/transfer?to=123456;amount=10000 (where 123456 is the number of their Cayman Islands account and 10000 is an amount that you previously thought you were glad to possess).
You retrieved that www.cute-cat-pictures.org page, so your browser will make that request.
Your bank cannot recognize this origin of the request: Your web browser will send the request along with your www.mybank.com cookie and it will look perfectly legitimate. There goes your money!
This is the world without CSRF tokens.
Now for the better one with CSRF tokens:
The transfer request is extended with a third argument: http://www.mybank.com/transfer?to=123456;amount=10000;token=31415926535897932384626433832795028841971.
That token is a huge, impossible-to-guess random number that mybank.com will include on their own web page when they serve it to you. It is different each time they serve any page to anybody.
The attacker is not able to guess the token, is not able to convince your web browser to surrender it (if the browser works correctly...), and so the attacker will not be able to create a valid request, because requests with the wrong token (or no token) will be refused by www.mybank.com.
Result: You keep your 10000 monetary units.
(Your mileage may vary.)
EDIT from comment worth reading by SOFe:
It would be worthy to note that script from www.cute-cat-pictures.org normally does not have access to your anti-CSRF token from www.mybank.com because of HTTP access control. This note is important for some people who unreasonably send a header Access-Control-Allow-Origin: * for every website response without knowing what it is for, just because they can't use the API from another website.
Yes, the post data is safe. But the origin of that data is not. This way somebody can trick user with JS into logging in to your site, while browsing attacker's web page.
In order to prevent that, django will send a random key both in cookie, and form data.
Then, when users POSTs, it will check if two keys are identical. In case where user is tricked, 3rd party website cannot get your site's cookies, thus causing auth error.
The Cloud Under blog has a good explanation of CSRF tokens. (archived)
Imagine you had a website like a simplified Twitter, hosted on a.com.
Signed in users can enter some text (a tweet) into a form that’s being
sent to the server as a POST request and published when they hit the
submit button. On the server the user is identified by a cookie
containing their unique session ID, so your server knows who posted
the Tweet.
The form could be as simple as that:
<form action="http://a.com/tweet" method="POST">
<input type="text" name="tweet">
<input type="submit">
</form>
Now imagine, a bad guy copies and pastes this form to his malicious
website, let’s say b.com. The form would still work. As long
as a user is signed in to your Twitter (i.e. they’ve got a valid
session cookie for a.com), the POST request would be sent to
http://a.com/tweet and processed as usual when the user clicks the
submit button.
So far this is not a big issue as long as the user is made aware about
what the form exactly does, but what if our bad guy tweaks the form
like this:
<form action="https://example.com/tweet" method="POST">
<input type="hidden" name="tweet" value="Buy great products at http://b.com/#iambad">
<input type="submit" value="Click to win!">
</form>
Now, if one of your users ends up on the bad guy’s website and hits
the “Click to win!” button, the form is submitted to
your website, the user is correctly identified by the session ID in
the cookie and the hidden Tweet gets published.
If our bad guy was even worse, he would make the innocent user submit
this form as soon they open his web page using JavaScript, maybe even
completely hidden away in an invisible iframe. This basically is
cross-site request forgery.
A form can easily be submitted from everywhere to everywhere.
Generally that’s a common feature, but there are many more cases where
it’s important to only allow a form being submitted from the domain
where it belongs to.
Things are even worse if your web application doesn’t distinguish
between POST and GET requests (e.g. in PHP by using $_REQUEST instead
of $_POST). Don’t do that! Data altering requests could be submitted
as easy as <img src="http://a.com/tweet?tweet=This+is+really+bad">,
embedded in a malicious website or even an email.
How do I make sure a form can only be submitted from my own website?
This is where the CSRF token comes in. A CSRF token is a random,
hard-to-guess string. On a page with a form you want to protect, the
server would generate a random string, the CSRF token, add it to the
form as a hidden field and also remember it somehow, either by storing
it in the session or by setting a cookie containing the value. Now the
form would look like this:
<form action="https://example.com/tweet" method="POST">
<input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
<input type="text" name="tweet">
<input type="submit">
</form>
When the user submits the form, the server simply has to compare the
value of the posted field csrf-token (the name doesn’t
matter) with the CSRF token remembered by the server. If both strings
are equal, the server may continue to process the form. Otherwise the
server should immediately stop processing the form and respond with an
error.
Why does this work? There are several reasons why the bad guy from our
example above is unable to obtain the CSRF token:
Copying the static source code from our page to a different website
would be useless, because the value of the hidden field changes with
each user. Without the bad guy’s website knowing the current user’s
CSRF token your server would always reject the POST request.
Because the bad guy’s malicious page is loaded by your user’s browser
from a different domain (b.com instead of a.com), the bad guy has no
chance to code a JavaScript, that loads the content and therefore our
user’s current CSRF token from your website. That is because web
browsers don’t allow cross-domain AJAX requests by default.
The bad guy is also unable to access the cookie set by your server,
because the domains wouldn’t match.
When should I protect against cross-site request forgery? If you can
ensure that you don’t mix up GET, POST and other request methods as
described above, a good start would be to protect all POST requests by
default.
You don’t have to protect PUT and DELETE requests, because as
explained above, a standard HTML form cannot be submitted by a browser
using those methods.
JavaScript on the other hand can indeed make other types of requests,
e.g. using jQuery’s $.ajax() function, but remember, for AJAX requests
to work the domains must match (as long as you don’t explicitly
configure your web server otherwise).
This means, often you do not even have to add a CSRF token to AJAX
requests, even if they are POST requests, but you will have to make
sure that you only bypass the CSRF check in your web application if
the POST request is actually an AJAX request. You can do that by
looking for the presence of a header like X-Requested-With, which AJAX
requests usually include. You could also set another custom header and
check for its presence on the server side. That’s safe, because a
browser would not add custom headers to a regular HTML form submission
(see above), so no chance for Mr Bad Guy to simulate this behaviour
with a form.
If you’re in doubt about AJAX requests, because for some reason you
cannot check for a header like X-Requested-With, simply pass the
generated CSRF token to your JavaScript and add the token to the AJAX
request. There are several ways of doing this; either add it to the
payload just like a regular HTML form would, or add a custom header to
the AJAX request. As long as your server knows where to look for it in
an incoming request and is able to compare it to the original value it
remembers from the session or cookie, you’re sorted.
The site generates a unique token when it makes the form page. This token is required to post/get data back to the server.
Since the token is generated by your site and provided only when the page with the form is generated, some other site can't mimic your forms -- they won't have the token and therefore can't post to your site.
The root of it all is to make sure that the requests are coming from the actual users of the site. A csrf token is generated for the forms and Must be tied to the user's sessions. It is used to send requests to the server, in which the token validates them. This is one way of protecting against csrf, another would be checking the referrer header.