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. I suggest you donate some of that to Wikipedia.
(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.
Access-Control-Allow-Origin
is a CORS (Cross-Origin Resource Sharing) header.
When Site A tries to fetch content from Site B, Site B can send an Access-Control-Allow-Origin
response header to tell the browser that the content of this page is accessible to certain origins. (An origin is a domain, plus a scheme and port number.) By default, Site B's pages are not accessible to any other origin; using the Access-Control-Allow-Origin
header opens a door for cross-origin access by specific requesting origins.
For each resource/page that Site B wants to make accessible to Site A, Site B should serve its pages with the response header:
Access-Control-Allow-Origin: http://siteA.com
Modern browsers will not block cross-domain requests outright. If Site A requests a page from Site B, the browser will actually fetch the requested page on the network level and check if the response headers list Site A as a permitted requester domain. If Site B has not indicated that Site A is allowed to access this page, the browser will trigger the XMLHttpRequest
's error
event and deny the response data to the requesting JavaScript code.
Non-simple requests
What happens on the network level can be slightly more complex than explained above. If the request is a "non-simple" request, the browser first sends a data-less "preflight" OPTIONS request, to verify that the server will accept the request. A request is non-simple when either (or both):
- using an HTTP verb other than GET or POST (e.g. PUT, DELETE)
- using non-simple request headers; the only simple requests headers are:
Accept
Accept-Language
Content-Language
Content-Type
(this is only simple when its value is application/x-www-form-urlencoded
, multipart/form-data
, or text/plain
)
If the server responds to the OPTIONS preflight with appropriate response headers (Access-Control-Allow-Headers
for non-simple headers, Access-Control-Allow-Methods
for non-simple verbs) that match the non-simple verb and/or non-simple headers, then the browser sends the actual request.
Supposing that Site A wants to send a PUT request for /somePage
, with a non-simple Content-Type
value of application/json
, the browser would first send a preflight request:
OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
Note that Access-Control-Request-Method
and Access-Control-Request-Headers
are added by the browser automatically; you do not need to add them. This OPTIONS preflight gets the successful response headers:
Access-Control-Allow-Origin: http://siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
When sending the actual request (after preflight is done), the behavior is identical to how a simple request is handled. In other words, a non-simple request whose preflight is successful is treated the same as a simple request (i.e., the server must still send Access-Control-Allow-Origin
again for the actual response).
The browsers sends the actual request:
PUT /somePage HTTP/1.1
Origin: http://siteA.com
Content-Type: application/json
{ "myRequestContent": "JSON is so great" }
And the server sends back an Access-Control-Allow-Origin
, just as it would for a simple request:
Access-Control-Allow-Origin: http://siteA.com
See Understanding XMLHttpRequest over CORS for a little more information about non-simple requests.
Best Answer
At the end of the day you have to "trust" the client browser to safely store user's data and protect the client-side of the session. If you don't trust the client browser, then you should stop using the web at all for anything other than static content. Even with using CSRF tokens, you are trusting the client browser to correctly obey the Same Origin Policy.
While there have been previous browser vulnerabilities such as those in IE 5.5/6.0 where it has been possible for attackers to bypass the Same Origin Policy and execute attacks, you can typically expect these to be patched as soon as discovered and with most browsers automatically updating, this risk will be mostly mitigated.
The
Origin
header is normally only sent for XHR cross-domain requests. Image requests do not contain the header.I'm not sure whether this falls under manipulated browsers or not, but old versions of Flash allowed arbitrary headers to be set which would enable an attacker to send a request with a spoofed
referer
header from the victim's machine in order to execute an attack.