Cross-site request forgery attacks (CSRF or XSRF for short) are used to send malicious requests from an authenticated user to a web application. The attacker can’t see the responses to the forged requests, so CSRF attacks focus on state changes, not theft of data. Successful CSRF attacks can have serious consequences, so let’s see how CSRF works and how you can prevent it.
Legitimate Cross-Site Requests
When you are browsing a website, it is common for that website to request data from another website on your behalf. For example, in most cases, a video that is shown on a website is not typically stored on the website itself. The video appears to be on the website but is actually being embedded from a video streaming site such as YouTube. That’s the idea behind Content Delivery Networks (CDNs), which are used to deliver content faster. Many websites store scripts, images, and other bandwidth-hungry resources on CDNs, so during browsing, images and script files are downloaded from a CDN source rather than the website itself.
While this improves the browsing experience, it might also be a source of a security problem if a website asks the web browser to retrieve data from another website without the user’s consent. If such requests are not handled correctly, an attacker can launch a cross-site request forgery attack – a type of attack that has made the OWASP Top 10 list of most critical web application security flaws several times.
How Can Cross-Site Requests Be Dangerous?
When a website requests data from another website on behalf of a user, there are no security concerns as long as the request is unauthenticated, i.e. the session cookie is not sent. However, when the user’s session cookie is sent with the request, attackers can launch a cross-site request forgery attack that abuses the trust relationship between the victim’s browser and the web server.
Combined with social engineering to persuade users to open a malicious link, CSRF attacks can have serious consequences – but how exactly do they work? Before we discuss CSRF vulnerabilities, let’s start with an overview of HTTP requests, cookies, and user sessions.
A Quick Introduction to HTTP Requests and Cookies
HTTP GET Requests
HTTP GET requests are used to request data from the web server. For example, when you enter a website URL in your web browser, you instruct the browser to send an HTTP GET request to the web server that hosts the website. The server then returns the response, and the browser renders it.
HTTP POST Requests
HTTP POST requests are used to send data to be posted on the web application. For example, when you submit a web form, such as a login or contact form, your browser generates an HTTP POST request. The request contains data submitted through the web form.
Automatically Generated HTTP GET and POST Requests
<img> tag automatically generates a GET request to the image link declared in the
img src attribute. Another example is an XHR POST request (AJAX request), used to automatically fetch search suggestions while the user is typing a query.
Web and Session Cookies
How Does Cross-Site Request Forgery Work?
Since cross-site requests do not need your permission, an attacker can abuse this and send requests without your consent and without you noticing. Let’s take a look at the following scenario which highlights why this can be a problem.
A Legitimate Web Form
Imagine a form on a website that allows you to change your email address. To change it, all you have to do is write your new address into an input field and click “Change” while you are logged in. If you are not logged in, you do not have a session cookie, and the form won't work. Let’s see how this input field works by looking at the underlying HTML code:
<body> <p>Your new email address</p> <form method="POST" action="mail.php"> <input type="text" name="email" placeholder="Change your email address"> <button type="submit">Change</button> </form> </body>
In the web form code above, there are three important HTML attributes:
method HTML attribute is used to specify which HTTP verb to use, and by default it is GET. When GET is used, all the parameter-value pairs are sent as part of the request in the URL, therefore appearing in the browser bar right after the question mark character, such as:
Such requests are logged in the web server’s log files and in the browser history. Therefore, the GET verb should only be used to transmit non-sensitive information and for actions that don’t change data (so no insert, update, or delete). For example, it could be used to indicate the page you are currently browsing, such as
In web forms, typically the HTTP verb POST is used, because it sends the data in the body of the request. Therefore there is no record of the data being sent in server logs or browser history. This makes it ideal for transmitting sensitive and large amounts of data, such as passwords.
The second HTML attribute is called
action, in which the target of the request is specified. This can either be a page on the website or an external one, on another domain. The value in the above code sample is
mail.php, which is the name of the PHP script that allows you to change your email address.
name attribute in the web form’s input field that contains the name of the parameter in which the data you submit will be stored. Therefore, if you type in firstname.lastname@example.org in the form and click the submit button, the value of the parameter
email@example.com and the web server will receive the following parameter-value pair:
The HTTP POST Request
When the form is submitted, the browser sends the following HTTP request:
POST /email.php HTTP/1.1 Host: example.com Cookie: SESSION=e29a31e41c9512a4bd Content-Type: application/x-www-form-urlencoded firstname.lastname@example.org
As you can see, there is a
Host HTTP header containing the hostname of the website the form is submitted to. There is also a
Cookie HTTP header containing the session cookie value which the web application uses to determine if you are logged in.
Abusing Web Forms in a Cross-site Request Forgery Attack
Create a Copy of the Web Form
With the above knowledge, it is easy to think of a scenario in which an attacker can change your email address to something of his choice. He can copy the web form and host it on his own web server, such as
http://www.attacker.com/evil.php. Below is an example of the new form:
<form id="csrf" method="POST" action="http://example.com/email.php"> <input type="text" name="mail" value="email@example.com"> <form> <script> document.getElementById('csrf').submit(); </script>
The self-submitting form is then placed at
firstname.lastname@example.org, as set in the
value attribute in the code above.
The Attacker’s HTTP Request
Below is the HTTP Post request that was generated by the attack. We’ve removed some unrelated request headers so it is easier to read:
POST /email.php HTTP/1.1 Host: example.com Origin: http://attacker.com Referer: http://attacker.com/csrf.html Cookie: SESSION=e29a31e41c9512a4bd Content-Type: application/x-www-form-urlencoded email@example.com
Here is a breakdown of the attacker’s HTTP POST request:
- The web browser issues a POST request.
- The host is the vulnerable website the user is logged in to, in our case
example.com. Note the
Refererheaders that show where the request is coming from – the referrer is
Cookieheader, which contains the user’s session cookie. Even though the browser request was initiated by a malicious script, the browser still sends the
Cookieheader along with the request because the request is for
example.com, for which the user has a session cookie. This also means that the web application will recognize the user’s authorized session.
- Below the cookie header is the
Content-TypeHTTP header which shows that the request was issued by a form.
- And at the bottom, as the post body, is the parameter-value pair. The parameter is
Tricking Victims Into a CSRF Attack
How can attackers trick their victims into a CSRF attack? Let’s assume the attacker publishes the form on
https://attacker.com/csrf.html. All he needs to do now is trick the user into navigating to the malicious website. This usually involves a little social engineering, for example sending a phishing email asking the victim to urgently visit this URL to restore access to their bank account.
So once the user visits
https://attacker.com/csrf.html, the form submission is triggered. The vulnerable website
https://example.com accepts the request and the email is changed to
firstname.lastname@example.org, since to the web application it seems like the victim submitted the form (because of the session cookie).
Now all an attacker has to do is use the password reset functionality to send a password reset email. Since the email was changed to
email@example.com, Bob will receive the email and can easily change Alice’s password to lock her out and take over her account.
Hiding the CSRF Attack from the Victim
For the victim not to notice the CSRF attack, the attacker can create another web page, for example
info.html, which contains information about a topic the victim is interested in. However, the web page controlled by the attacker can contain a hidden iframe pointing to
https://attacker.com/csrf.html. When the user visits
info.html, the form on
csrf.html is automatically triggered, without any visible indicator to the victim.
Attackers typically use CSRF attacks in login forms, such as password or email change forms, to hijack their victims’ accounts or create a new admin user on a web application.
How to Prevent Cross-Site Request Forgery Attacks
An attacker can launch a CSRF attack when he knows which parameters and value combination are being used in a form. Therefore, by adding an additional parameter with a value that is unknown to the attacker and can be validated by the server, you can prevent CSRF attacks. Below is a list of some of the methods you can use to block cross-site request forgery attacks.
Implement an Anti-CSRF Token
An anti-CSRF token is a type of server-side CSRF protection. It is a random string that is only known to the user’s browser and the web application. The anti-CSRF token is usually stored inside a session variable. On a page, it is typically in a hidden field that is sent with the request.
If the values of the session variable and the hidden form field match, the web application accepts the request. If they do not match, the request is dropped. In this case, the attacker does not know the exact value of the hidden form field that is needed for the request to be accepted, so he cannot launch a CSRF attack. In fact, due to same origin policy, the attacker can’t even read the response that contains the token.
Use the SameSite Flag in Cookies
The SameSite flag in cookies is a relatively new method of preventing CSRF attacks and improving web application security. In the above scenario, we saw that
https://attacker.com/ could send a POST request to
https://example.com/ together with a session cookie. This session cookie is unique for every user, so the web application uses it to distinguish users and to determine if they are logged in.
If the session cookie is marked as a
SameSite cookie, it is only sent along with requests that originate from the same domain. Therefore, when
https://example.com/index.php wants to make a POST request to
https://example.com/post_comment.php, it is allowed. However,
https://attacker.com/ can’t send POST requests to
https://example.com/post_comment.php, since the session cookie originates from a different domain, so it is not sent along with the request.
Vulnerability Classification and Severity Table
|Classification||ID / Severity|