Web Security 101: An Interactive Cross-Site Request Forgery (CSRF) Demo

A hands-on beginner's guide to what CSRF attacks are and how to prevent them.

Looking for an introduction to Cross-Site Request Forgery (CSRF)?

This post will be a little different - instead of telling you what it is, I’m going to show you. Ready?

Setting the Scene

You’re a responsible, hardworking person. You’ve saved up your money over the years at Definitely Secure Bank®.

The Definitely Secure Bank logo.

You love Definitely Secure Bank - they’ve always been good to you, plus they make it easy to transfer money via their website. Sweet, right?

To get in character, let’s have you open up your online banking portal and look around. Click here to open Definitely Secure Bank’s website and login. Use any username and any password you want (don’t worry - it’s definitely secure). Keep that tab open for the rest of this post.

Once you’ve logged in, you should see a landing page that looks something like this:

dsb 1

Nice! You’ve got $20,000 saved up! It’d be a shame if someone stole it…

Notice also the ”Make Transfer” button. That’s a nice feature the Definitely Secure Bank website offers - you can send money online with just one click! If you want to give it a go, try sending a dollar to see what happens:

dsb 2

One Fateful Day…

…you get an email titled: ⚠️ Limited Time Offer! 🎁 Get a $100 Visa Gift Card Now!! ⚠️

Woah, you think to yourself, I could use a free $100 gift card! What could go wrong? You open the email and click the link:

Yes, I actually want you to click this button. Do it for the purposes of the demo.

Little do you know, there’s no $100 gift card (what?!?!) - in fact, you’ve just lost $10,000. Don’t believe me? Go check your Definitely Secure Bank tab (the one I told you to keep open for this post). It should look something like this:

dsb 3

No!!!!!!!!, you scream (silently in your head). Impossible! How could this happen??

We’ll get to that in a second. First, let’s take a step back: that was a CSRF attack. In short, Cross-Site Request Forgery is a web security exploit where an attacker induces a victim to perform an action they didn’t mean to. In this case, the attacker tricked you into unintentionally transferring them money.

How It Happened

The first step was that you logged in, which means you have a session cookie set.

You might be thinking, Wait, what? 🍪🍪🍪 Cookie?. In case you are, we’re going pause momentarily for:

A crash course on [web] cookies

Feel free to skip this part if you don’t need a refresher.

Cookies are data sent from a web server that gets stored by the user’s browser and subsequently included on future requests to that server. For example, when logging into a site, something like this happens:

  1. Your browser sends your username/password to the server.
  2. The server verifies your username/password.
  3. Upon success, the server responds with a custom cookie for you, often referred to as a session cookie.
  4. On future requests, your browser includes the session cookie. The server checks your session cookie each time and now knows who you’re logged-in as.

This is why you can login to Facebook, close it, re-open it later, and still be logged in - your session cookie was saved by your browser!

More reading on web cookies: MDN, Wikipedia.

Now, back to… How It Happened

Okay, so we’ve established that your session cookie was set because you’d already logged in. Let’s get to the juicy part.

Here’s the Javascript that ran when you clicked that button:

const body = new URLSearchParams('amount=10000&description=Gotcha!&to=Evil-Scammers');
fetch('https://dsb.victorzhou.com/transfer', {
  body,
  credentials: 'include', // include cookies, despite being a cross-origin request
  method: 'post',
  mode: 'no-cors',
});
Uses the Fetch API
.

This sends an HTTP POST request to the Definitely Secure Bank’s (DSB) /transfer endpoint with these parameters:

ParameterValue
amount10000
descriptionGotcha!
toEvil-Scammers

Coincidentally, that happens to be exactly how the DSB website’s nifty “Make Transfer” feature works! You can see the code the DSB website runs when you click “Submit Transfer” and compare for yourself.

But how did this code manage to steal your money while including neither your username nor password? Because you’re logged in. Browsers can send cookies to one origin (the DSB site) even when the request originates from a different origin (the attacker’s site they tricked you into visiting).

A recap of what happened, in order:

  1. You logged into DSB, so your browser had your session cookie stored.
  2. You visited a link (simulated by clicking that button in this post) controlled by the attacker.
  3. The attacker’s site sent a request to the DSB server in the exact format that the actual DSB website would’ve used.
  4. The DSB server receives a completely legitimate looking request and processes the transaction.

There it is. Cross-Site Request Forgery.

Wait, what about CORS?

A common misconception is that Cross-Origin Resource Sharing (CORS) is a CSRF prevention mechanism. Let’s clarify a few things here.

First: CORS relaxes the Same-Origin Policy (SOP), a critical security measure that prevents scripts on one site (e.g. the attacker’s site) from accessing sensitive data on another site (e.g. the Definitely Secure Bank portal). If something was protecting you from CSRF, it would be the SOP.

However, in our case the SOP allows the POST request to be sent and only blocks the attacker from reading the response, which doesn’t matter! As long as the POST request to make the transfer goes through, the damage is done.

So how do you prevent CSRF, then?

The most common method of preventing CSRF is by generating a secret random string, known as a CSRF token, on the server, and checking for that token when the client performs a write. For example, here’s one way the DSB site could protect against CSRF:

  1. Generate Token (server): when a user visits the “Make a Transfer” page, generate a CSRF token on the server and include it as a hidden field in the transfer form.
  2. Submit Token (client): when the user submits the form, the CSRF token is automatically included in the request.
  3. Verify Token (server): the server verifies that the CSRF token matches the one it originally sent to the user.

The data this user would send in their legitimate request might look like this:

ParameterValue
amount100
descriptionThis is a real transfer.
toThe intended recipient
csrf_tokenK2JY9JjZqF77Yil8
A legitimate transfer request.

As long as the CSRF token isn’t transmitted using cookies, an attacker would have no way of including it in a forged request:

ParameterValue
amount999999
descriptionYou got hacked!
toEvil-Scammers
csrf_token??????????
An attacker's transfer request, which would fail because the CSRF token is invalid.

Note: Many web frameworks already have CSRF prevention built-in. Be sure to check for existing solutions before you implement it yourself!

While CSRF tokens work well, they’re just the tip of the CSRF prevention iceberg. If you’re interested in reading more, I recommend this CSRF Prevention Cheat Sheet. Alternatively, if you want to learn more about another dangerous web security hole, check out my introduction to Cross-Site Scripting (XSS).

Thanks for reading, and stay safe!

I write about ML, Web Dev, and more topics. Subscribe to get new posts by email!



This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

This blog is open-source on Github.

At least this isn't a full screen popup

That'd be more annoying. Anyways, subscribe to my newsletter to get new posts by email! I write about ML, Web Dev, and more topics.



This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.