Cross-site request forgery (CSRF) attacks can be difficult to wrap your head around. There can be a lot of variation in these attacks, so it can be hard to see what truly lies at their core.
In short, cross-site request forgeries involve attackers tricking your web browser into sending their forged requests to the server. Because these requests seem to come from your browser and include your authentication data, the website thinks that they are legitimate.
The server complies with the request, which could be to change your password, change the email associated with your account, or even steal money from your bank account. This can all happen without you clicking on anything, and even without your knowledge. Ultimately, CSRF attacks can have devastating consequences, as they can grant attackers complete control of accounts.
Cross-site request forgery attacks are also known as hostile linking or session riding. The CSRF acronym is often pronounced sea-surf, and the C is occasionally replaced by an X to form XSRF.
If you are completely stumped about these attacks, or have a rough idea, but don’t completely understand how they work, don’t worry. We’re going to introduce each of the key concepts slowly and in detail, so that by the end of the article you should have a solid grasp of what cross-site request forgery attacks really are.
Feel free to skip over any parts you are already familiar with–not everyone has the same background knowledge, and it’s better to cover more than is necessary for some people, rather than under-explain key concepts, and then have you frantically searching the web to answer your questions.
What are Cross-site request forgery attacks?
Let’s start by breaking down the name. In this case, it’s actually quite illustrative of what these attacks are at their core. On one hand, we have “cross-site”, which alludes to the fact that the attack originates from one site, but acts on another.
The other element is “request forgery”. This is pretty obvious – these attacks somehow involve someone forging requests. So when we bring these two concepts together, we have attackers forging requests that originate on one site, but act on another.
Cross-site request forgeries (CSRF) and state changes
One of the first things you need to wrap your head around is that cross-site request forgeries involve changing the state of data on a website’s server. This simply means that the attacker sends a request to modify the data on the server on the victim’s behalf. If the request is accepted, the data is changed.
This is because CSRF attacks don’t give hackers any visibility into the server’s response to the victim. Forging requests that asked for sensitive data would be pointless, because the sensitive data would be sent to the victim, not the attacker.
While CSRF attacks don’t allow hackers to directly steal data, changing the state of data on a server can still be a powerful move that causes significant harm to the victim. An attacker could forge a request that gets sent through the victim’s browser, asking the server to send funds from the victim’s account into one controlled by the attacker.
In this case, the request is to change the state of the victim’s bank balance. If it is accepted by the server, the attacker can make off with the victim’s money. Even though the server never sends any information to the attacker’s computer, the request tricks the bank into sending money into the attacker’s account.
Similarly, attackers can send forged requests through the victim’s browser that ask to change the victim’s password to something the attacker knows. If the server accepts the request, it makes it easy for the attacker to take over the victim’s account once the password has been switched.
Attackers can achieve the same outcome if they forge a request to change the email address on the victim’s account to one that the attacker owns. They can then ask the site to reset the password, and the authentication email will go straight to the attacker rather than to the account owner. From this point, they change the password, and the account is in their hands.
While the cross-site request forgeries in these attacks don’t directly involve the website sending user data to the attacker, these latter two examples make it easy for the attacker to steal data once they are in control of the account. They show just how devastating changing the state of data on the server can be.
The necessary conditions for cross-site request forgery (CSRF) attacks
Cross-site request forgeries require a range of conditions to be met in order for these attacks to be viable options. These include:
They need to target websites where users can request changes to the state of data on the server
CSRF attacks are used to circumvent the authorization and authentication processes that are normally in place. This means that these attacks are pointless on static websites and sites where there are no user accounts that can make requests to change the state of data on the server.
Attackers will only bother with CSRF attacks if these server state changes can actually benefit them, such as if they can make funds transfers, or take over accounts that have valuable access or information.
Victims need to be logged in at the time of the attack
Many websites use session cookies with unique identifiers to grant users access to authorized pages. These are generated when victims first log in and only last for the session, which means that victims need to be logged in at the time the forged request is sent in order for CSRF attacks to succeed.
When they are logged in, attackers can trick the browsers of their victims into sending requests that the user did not intend to make. These malicious requests are accepted by the server because they include the victim’s session cookie, which makes them seem legitimate to the server.
The majority of cross-site request forgery attacks involve taking advantage of session cookie authentication, however, some less-common authentication systems can also be compromised through CSRF attacks.
Victims must visit the page that hosts the forged request
The attacker needs to find a way to make victims visit a specific page that hosts the forged request. This can be relatively easy in the case of stored cross-site request forgeries, which we will discuss later. Otherwise, it may require luring victims to a separate website, and tricking them into clicking a link or having the malicious request triggered automatically via scripts.
The website needs predictable parameters
In order to forge a request that will actually be accepted by the server, the attacker needs to know what these requests are supposed to look like. Let’s explain why this is important with an analogy.
If you wanted to forge a check and cash it in at the bank, you would first need to know how to properly write a check. If you had written the name in the box for the date, the account number as the amount, and made a whole bunch of other screw-ups, the bank would simply reject your forged check, and you would never get the money you desired.
It’s similar if an attacker wants to be able to change the state of the server’s data in their favor. They need to know how the website typically formats requests if you want to be able to forge a request that will actually get accepted. If an attacker tried to change the password for an account, but the values were incorrect or in the wrong order, the request would be rejected, and they would have wasted their time.
This means that for CSRF attacks to succeed, the website needs to have predictable parameters. If every request follows the same predictable format, an attacker can examine previous requests and abuse this knowledge to forge a request that’s actually in the correct format. If the request requires passwords, IDs, or secret authentication values that the attacker cannot guess, they will be unable to successfully perform a cross-site request forgery attack.
Back to basics: Understanding the security ecosystem that CSRF attacks circumvent
Now that we have covered the basic conditions necessary for cross-site request forgery attacks, it’s a good opportunity to take a step back and examine the important elements of the security landscape that these attacks attempt to get around. It can be hard to truly understand why attackers turn to CSRF attacks if you aren’t familiar with the systems that are already in place.
Accounts
Unless you are Amish or a Luddite, you probably have a bunch of different accounts for the various web services you use. You presumably have accounts for your email, social media, banking and much more.
But why do you have these accounts in the first place?
Because you often do important things on these websites. For an online banking website to actually be useful, it needs to allow you to check your balance, send new transfers and perform a range of other actions. You may also need to change your password at times, and update your personal details.
While it’s essential that websites allow you to do each of these tasks, it’s critical for the sites to only allow you to do them. If anyone could use the banking website to send your money to themselves, or change your password, the chaos this would result in would be far more detrimental than the benefits the service offers.
Accounts help to control the information and systems that you are allowed to access. If an email provider simply had an honor system and stored all email in a database that everyone could access, no one could be granted any privacy in their communications.
When we are dealing with important information, it’s often best to have user accounts that limit what each individual can access. This is especially important when privacy is crucial, or if someone else’s changes could have negative effects.
As an example of where user accounts make sense, most of us don’t have Wikipedia accounts, because most people aren’t Wikipedia editors. However, we can all still visit the website and learn about whatever interests us. There isn’t too much damage you can do by simply visiting and reading the site.
However, if Wikipedia didn’t require accounts for its editors, the website would quickly deteriorate. Editors can create and alter the information, so there need to be protections in place that stop people from defacing the site and writing gibberish. While Wikipedia doesn’t always succeed in preventing these outcomes, it would be a lot worse if there were no control mechanisms in place.
Authorization and authentication
So know we know why we need user accounts. But we also require systems in place that control the information and resources that each user is authorized to access. We need ways to authenticate each user as well. We need to be able to determine that people are who they say that they are, and aren’t just impostors.
Once again, you wouldn’t be too satisfied if your online banking website just had an honor system in place. What would prevent hackers from simply saying that they are you, and then sifting through your data they aren’t supposed to access? What would stop them from going into your account and sending all of your money to themselves?
Instead of this ludicrous free-for-all, we have authentication systems like usernames, passwords, and two-factor authentication that restrict people so that they can only access the resources they are authorized to access. As long as you have a strong password, you keep the password a secret, and you protect your second authentication factor (ideally an authentication app or a security token, because SMS authentication isn’t that safe), it’s pretty hard for an attacker to pretend that they are really you.
In most cases (these systems aren’t foolproof, but good enough for most purposes when implemented and used properly), if a website has appropriate protective measures in place and you take your security responsibilities seriously, you can be relatively confident that it would be a significant challenge for an attacker to impersonate you. This difficulty keeps your account balance from being drained, your emails from being read, and strangers from posting crazy rants on your social media.
In a simplified system that was designed to protect your data, you may be expected to prove your identity and authenticate yourself by logging in every time you visit a new page within a website. You would log in once to get to your account overview page. If you wanted to check a transaction from two months ago, you would probably have to visit a new page and log in all over again. If you wanted to make a new funds transfer, you would have to log in once more.
Obviously, this would quickly become tedious and make the internet far less usable. Thankfully, developers have come up with systems to prevent you from having to do this for each and every page. The most common technique involves session cookies.
Session cookies
When you successfully log in to an account by authenticating yourself with the correct username and password, most website servers will insert a session cookie into your browser. The session cookie includes a unique session identification number, and the server stores its own copy of this ID. These session IDs are complicated enough that it’s unfeasible for an attacker to simply guess them. They also expire at the end of each session, so the next time you log in, a new session ID will be planted in your browser.
The benefit of session cookies is that instead of being forced to prove your identity by logging in each time you visit a new page. Instead, the website’s server will simply look for the session ID in the cookie it planted when you logged in.
Whenever you click to visit a new page within the website, your browser will send a request to the website’s server. Each of these requests includes the cookie with your session ID. The server receives the request along with the cookie and compares the session ID along with the value it stored when the session was created. If the two match, it assumes that it is the correct user, and grants you access to the resources you are authorized to access.
With the session ID stored in the cookie in your browser, you can make your way across the website’s pages and perform a variety of actions, like changing your password, updating your email address, and setting up new funds transfers. With this cookie-based authentication system in place, you don’t have to log in separately for each task.
While this system generally works quite well, it’s also what cross-site request forgeries try to take advantage of if the right precautions aren’t in place.
In cases where the unique session ID is the only thing necessary to prove your identity once you have already logged in, it follows that if the attacker can figure out how to manipulate this ID without your knowledge, they could use it to perform some of the actions that only you have the authorization to perform. They may be able to transfer your money, change your password or the email address that is registered with your account, or commit a range of other attacks.
HTTP requests
Now that we’ve covered the absolute basics of how your accounts and the information within them is secured, we can look into how data is actually transferred between clients and servers over HTTP.
HTTP is one of the web’s most important protocols. When you visit a website, your web browser– the client–sends a HTTP request to the website’s server. The server receives the request, then generally sends you back the resources you asked for in its response, or it makes the changes you requested.
In the case of visiting Comparitech.com, your browser would start by finding the site’s server. Then it would send the server a HTTP request asking it to send a copy of Comparitech’s home page. If the server approved the request, it would send you a copy of our home page via a series of data packets. Your browser would collect these packets, then use them to show you a copy of our website. You could then peruse our content at your leisure.
If you then clicked on one of our articles, such as the one you are currently reading, your browser would send another HTTP request to the server. The server would usually approve it, then send you the copy in a large number of data packets. Your browser would then assemble the data into this article for you to read.
If you were browsing the internet in a relatively normal fashion, the servers you make requests to will tend to approve the majority of your requests. However, there are also a number of reasons for them to deny requests. Servers may not be able to locate the resource you are trying to access and return an error message in response to your request.
In another case, a request may not include the necessary authentication information to access a specific resource. In this case, the server will deny the request. One example of how this could happen is if an attacker tried to access your bank account, without the correct session ID that indicates they actually have authorization.
Cross-site request forgery attacks are conducted with forged HTTP requests. In order to succeed in their attacks, hackers need to figure out ways for their forged requests to be accepted by the server.
The different types of HTTP request
There are a variety of HTTP requests which perform separate functions. They include:
GET requests
This type of request is used to request resources from a website’s server. In our earlier example of you visiting the Comparitech.com website, your browser’s request to our server was a GET request. This is because you were asking the server for data.
When implemented correctly, GET requests should only retrieve data. Despite this, sometimes poor implementations will allow them to change the state of data. As you will see in our GET requests in CSRF section, this can cause security issues.
One of the reasons for this is that GET requests are logged by servers, stored in the browser history, can be cached, and can also be bookmarked. This makes them unsuitable for sending sensitive data.
POST requests
Post requests are used to submit data to servers and can change the state of the data they store. The content of these requests is sent in the body, they are never cached, can’t be bookmarked and don’t remain in the browser history. This makes them more appropriate for sending sensitive or valuable data.
A good example of a POST request that you will be familiar with occurs when you fill out website submission fields, such as contact forms. When you click the submit button, the data you entered into the input fields is sent as a POST request.
Other types of HTTP requests
- PUT requests – This type of request either replaces an existing resource or creates a new one if it didn’t already exist.
- DELETE requests – A DELETE request will delete the specified resource.
- HEAD requests – These requests ask the server for the metadata for a specific resource. It’s important to note that these requests don’t also seek the body data as GET requests do. HEAD requests can be used for things like finding out when a resource was updated.
There are also TRACE, PATCH, OPTIONS and CONNECT requests. We won’t cover them here, but if you are curious, you can learn about them in this article from Mozilla.
GET and POST requests are the most common in CSRF attacks, so we will give you examples of how each of these attacks work.
POST requests in CSRF
We will begin with an example of how POST requests can be manipulated to compromise a victim’s account. As we mentioned, POST requests are used to submit data in online forms. Let’s say yourfavoritewebsite.com uses a form for you to change your password. Of course, this is a necessary feature, because it’s important for people to be able to change their passwords if they suspect that their account may be compromised.
Forging the request form
The input fields for the password change could look something like this in HTML:
Note where it says method=“POST” which tells us that this is a POST request. POST requests are important for sensitive data like passwords because they send the data as part of the body, which isn’t logged or saved in the browser. This helps to keep new passwords confidential.
If you look to the left, you will see that it features the attribute action= “#”. This specifies the target of the request. The hash symbol is a placeholder.
You will also notice that the form frequently says name=. This attribute contains the name of the parameter for the data you will submit. In this example name=”password_new” and name=”password_conf” align with where the form would say New password and Confirm new password, respectively. If you enter password123 in both fields, it will be set as the new value for the name=”password_new” and name=”password_conf” parameters.
When you submit the form, your browser will send a POST request that includes the following headers, among others:
- HOST: yourfavoritewebsite.com
- Cookie: SESSION=872e98c2dab1974e75
The second header is the unique session ID that was created when you logged into the website.
Everything we’ve shown above is relatively normal and necessary for allowing users to change their passwords when needed.
Let’s imagine that an attacker visits the website from their own account, goes to the page to change their password, right-clicks anywhere on the page, then clicks on Inspect in the menu that pops up. This will bring up the underlying code for any page–you can try it for yourself on any web page.
Doing this on the page for changing passwords, can easily grant the attacker access to the form’s code. They could then copy it and make their own modifications, in order to forge a request that changes the victim’s password.
The attacker would add in the necessary elements to make the form function properly, such as the HTML tags, head and body. They would also write some JavaScript to make the form submit automatically so that the attack could proceed without the user having to purposely send through the form. This makes the attack even more likely to succeed.
In addition to this new code, the attacker would add in the page’s address as the target for the action attribute. They would also add in new values for name=”password_new” and name=”password_conf”. The attacker would set these values to whatever they want the new password to be.
The only thing that matters is that the password is something that they know. In this case, “csrfattack” is entered into both input fields as the new password. The key parts might look something like the following: