How your website will be hacked if you have no CSRF protection

As a CEO of an outsourcing software development company I've seen a lot of legacy code from previous developers for pretty successful projects which had large user audiences, and I must admit that security very often is the last thing that developers care about.

I believe if someone tells you that you should apply this or that type of protection, you will not make it right unless you hack your (or other) site by yourself first.

In post, I will provide simple examples which will help you to check whether users of your web sites are vulnerable to CSRF attacks. I will start with an old-school example which aim is to understand CSRF issue, and then will end with really important and popular case about hacking XHR/Fetch calls.

Assume your site has a form on some page which sends USD from user balance to defined credit card number:

<form action='/send_usd/' method='POST'>
<input name='card_address' type='text'/>
<input name='amount' type='number'/>
<button>Send USD</button>

CSRF Victim website

When user presses Send USD button, browser will make HTTP POST request with a Content-Type=application/x-www-form-urlencoded header to URL and attach Session or JWT cookie (We assume that site uses Cookies to authorize requests). With this request browser sends a cookie which server set on login request so server understands that user is John Doe. And John Doe has some balance in database which allows to send (withdraw) an amount. Let's imagine that cookie was set for 2 days since login (Or JWT token in cookie is valid for this period).

Now some hacker creates a site https://kittie-images.example/ and places a next form with hidden inputs on home page:

<form action='' method='POST'>
<input hidden name='card_address' type='text' value='HACKERS CARD'/>
<input hidden name='amount' type='number' value='1000'/>
<button>Show me the most awesome kittie</button>

Hackers kittie website

John likes kitties, so at Jan 3d, on his weekend, he googled for Kittie images and found this great website, he does not know it was created by hacker. By the way on Jan 2nd John logged in to to check all is good with his money.

When John wanted to see a new kittie he clicked Show me the most awesome kittie button, at this moment, without John knowing, $1000 USD disappeared from his balance at

And the reason – browser attached cookie when he sent request to even for this call which was made from different domain. Thing is that browser stores cookies per domain, and when request is done by <form> then most of browsers attach cookies associated with domain defined in action attribute.

So this is basic Cross-domain attack.

NOTE: I just tested this today in Firefox version 84.0.2 (latest for today) and it worked. Chrome or Safari might block it with features like cross-site tracking protection or defaulting cookies to SameSite=Lax released in early 2020 (see at the end of hint), but it is easy to disable because this feature still brakes some websites. Plus there are a lot of old browser versions on machines without auto-update. Also browsers will never hurry up to fix this due to compatibility reasons.

Self development is always about making right conclusions, so lets do it here:

An advanced facts:

So how to protect from it? Implement CSRF.

CSRF implementation

Most of secure websites implement CSRF for all their HTTP requests that change a data on server. Also many CMSs or frameworks like Django implement it for you. By the way security is a very good reason to use framework with CSRF protection when you are not super experienced in such things. Typical messages for CSRF errors in different sites/frameworks:

Such errors mean that site was not able to perform CSRF validation, which happens in some case. But I want to show you typical algorithm.

To implement CSRF you need to generate some unique big word when user logins to a website and set it to cookie along with JWT/session cookie. Then on each request include the same word in input when you render form on backend, or read from document.cookie and attach in another header if you are making XHR form JavaScript. In our example we need to render it to from:

<form action='/send_usd/' method='POST'>
<input hidden name='csrftoken' type='our-long-long-random-word-generated-at-login'/>
<input name='card_address' type='text'/>
<input name='amount' type='number'/>
<button>Send USD</button>

Then on each call just compare two tokens: one from cookie request header and another from form (or from header if you validating XHR). If they are same – you might be calm that request comes from your domain. Otherwise, send error to user. But don't suspect hacker here, show nice understandable message and ask to refresh a page, sometime it might happen because user just logged in again in another tab, and in current tab you just have an old CSRF token.

😢 Very sad that a lot of sites show "technical" version of this error like csrf token mismatch. Does they think John knows what is CSRF? Why should he know it? Or can't verify csrf token authenticity. Hey, he just logged in on another tab, he should not be aware about some tokens at all, it is your responsibility. Say him at least:

Something went wrong, please try refresh a page


🤔 Calling things right: when we and a lot of websites and a lot of frameworks like Django call it csrftoken, some people say that it is bad name. Because CSRF is attack, and aim of this token is anti-CSRF, so better call it anticsrftoken, or csrfprotecttoken

Example of attack when SOP does not save you

I have a detailed POST about CORS and SOP, you can check it here: SOP and CORS like I am 5 .

Here I will show the real example which might be a case for a lot of new websites which use SPAs (React, Vue, Angular) and some APIs on backend.

Imagine developer of now thinks that old-school form is a reason of disappeared money, so he have read somewhere that SPA and REST API is more secure setup for a websites. So instead of CSRF implementation he replaced form submission with next javascript from his SPA:

fetch('', {
headers: {
'Content-Type': 'application/json'
body: JSON.stringify({
"card_address": this.address,
"amount": this.amount
}).then(response => response.json()).then( (data) => {
// inform user that money were sent

He still uses Session/JWT stored in cookies, and all still works good for John. On backend developer parses a JSON and performs a transaction. Do you think John is safe now?

Hacker sees this change in developer tools and adjusts his form the next:

<form ENCTYPE='text/plain' action='' method='POST'>
  <input hidden name='{"card_address":"HACKERS ADDRESS", "amount":"1000", "igoredKey": "a' value='bc"}'>
  <button>Show me the new awesome kittie</button>

And clicking button will send POST to with attached Session/JWT cookie and next body:

{"card_address":"HACKERS ADDRESS", "amount":"1000", "igoredKey": "a=bc"}

igoredKey is needed to neutralize equality sign (=) send from form, because text/plain forms are serialized to KEY=VALUE format)

To fix it:

Cheap CSRF protection implementation with SameSite=Lax (Alternative to CSRF token)

Instead of tokens implementation you can do much simpler thing, when you set a cookie from backend – add SameSite=Lax attribute to this cookie. You can read more details here: SameSite on

When SameSite is set to Lax value, then cookies will be attached only when requests are made from same origin. Downsids:

Also browsers plan to include SameSite=Lax by default very soon. For today it is only 66% of browsers (Caniuse defaulting to SameSite=Lax) This means that for today, it is enough to not set SameSite attribute at all and your browsers will not need CSRF protection at all, you can read more Chrome’s SameSite cookie update.

Be secure, be protected and take care about your users! Good luck👍

CSRF Attack

#csrf #security
Ivan Borshchov profile picture
Jan 07, 2021
by Ivan Borshchov
Did it help you?
Yes !

Best related