CSRF attack prevention
Before explaining how to combat CSRF (Cross Site Request Forgery), a quick explanation of the technique behind it is in order.
A cross site request forgery relies on a user visiting a malicious site, shortly after they have logged into a genuine site, and whilst they still have a session cookie active with the genuine site.
By making the user's browser send malicious requests directly back to the genuine site, the malicious site can exploit the fact that the user is already logged in, to effectuate such things as placing orders in the user's name, sending emails using the user's credentials or posting comments to other users in what may well be a trusted user's name. The list of exploits is endless and only really subject to the vulnerabilities of the site being attacked.
Ways to make the user visit the malicious site whilst still being logged into the genuine site includes phishing, posting of links in comments on the genuine site, or even just "trial and error" by posting links on sites that may also be frequented by users of the genuine site.
The limitation of the CSRF attack is that it is always "blind". The attacker cannot see what the application responds with, or what the current state of the session is; due to restrictions imposed by browser security models that say that a request from one server (domain) cannot be sent to another.
CSRF defense techniques
How to best protect your site against CSRF attacks depends on how it was written. Generally speaking, most applications perform actions as a result of an HTML form being posted to the site. Some sites also respond with actions to a GET request
For example: "http://www.mysite.com/delete.jsp?orderToDelete=12345"
This example will focus on protecting applications that use a form POST. This is done by adding a hidden field to every form presented by the application. This hidden field contains a random value that is unique to the specific session of the user. We will require that this field is always present on a form POST, making it virtually impossible for a malicious site to second guess what a valid POST request might look like.
The technique for protecting a site that uses GET requests is similar, simply requiring the addition of an additional URL parameter to every URL that takes parameters, instead of a hidden form field.
Planning the rules
The first step in implementing our CSRF defense is to create a simple plan of action i.e. what do we intend to do, and how do we wish to go about doing it. It is a good idea to write this down in plain English and then use that text as a guide whilst designing the rule structure. In this case, the plan reads as follows:
If a POST request comes in whilst there is an active session, then make sure it has our hidden field, and that it is the hidden field we have generated for that session. If the field is not present, we should respond to the user with an HTTP Status code of 403 (Forbidden).
Whenever a new page is provided by the application, make sure we add a large random number as the hidden field to every form presented by the application. The large random number we use should be generated once for the session and then be stored in it for easy reference and good performance.
That sounds easy enough; so, let's begin...
Getting started
Create a new repository named "CSRF Example" and add a new rule set named "CSRF".
Filter out static content before it hits the core rules using a Name Splitter and Switch rule as shown:
The Name Splitter conveniently extracts the extension of the object being requested using the following properties:
The Switch rule operates on the EXT variable. By adding new chain points for each type of static content they are eliminated from reaching the rule set.
As we are dealing with Web Applications, and we need to know information such as the method used (POST/GET), the first step is to add an HTTP Request Tracker rule from the HTTP group in the rules catalog to the CSRF rule set:
A good technique for rule writing is to start by determining the "flow" of events or pages that will subsequently have rules applied to them.
In our case we have two flows:
The verification of the forms
The addition of the form fields.
So, our next action is to add a Sequencer rule from the Flow group in the rules catalog:
Implementing step 1
Now, the first step in our written plan is to check if we are dealing with a POST request in the session, and if the form posted has our hidden field. The first part is very easy:
Only the If Condition requires some properties:
The next step is simple. We need to look up the current hidden field from the session:
Once again, there are not many properties:
The variable names and values we have chosen are arbitrarily selected, although they should be meaningful and memorable.
In this example, we have decided that the hidden field is stored with a session key named "CSRF.key
" and that the hidden field on all forms is named "CSRF
". We could have chosen any names as long as we use them consistently when we add the field to the form and store the session key.
All that is left for the first step is to make sure that if the key doesn't match, then the user receives a 403 error.
Once again, the properties are very simple:
We use a Set Completed rule after the response, as once we have decided that the user should be rejected, there is no need to proceed with the rest of the rule set. Instead we simply terminate the flow.
Implementing step 2
We are now ready to implement the second part of the plan. The first step in doing so is getting the actual response from the server so that we can add the hidden field if we need to.
The HTTP Server Execute rule takes care of this, even if you are writing rules using a built in forwarding proxy.
Once again, the properties are very simple as we are just interested in the application response:
Once again, we need to check if a session is present, but after the HTTP Server Execute rule, as that rule may in fact result in a session being created:
If there is a session, then we need to add our unique CSRF key to it. The first step in doing that is to see if we already have that key:
Once again, not many properties:
If we don’t have it, we need to create it, which is easy:
The properties for these rules are as follows:
The session key we use is the same "CSRF.key
" that we used in step 1.
All that remains now is to add the field to the form and send the response back to the user.
Thankfully there is a dedicated rule that handles the first problem, the "Insert Hidden Field" rule.
Note that we are handling various loose ends too: connecting a Session not found to the HTTP Response, and connecting the existing session key to the Insert Hidden Field rule.
The final properties that must be set are as follows:
Testing
Our rule set is now complete, and we are ready to test it. A good sample application for this test is the Qwerty application. Create a configuration for the test named "CSRFTest" and set it as follows:
(Only relevant sections shown)
Once you have set up your configuration, deploy it to the Qwerty demo server and try testing it.
You will see in the Qwerty application, in the "Set up 3rd Party Accounts" page, that there is now a CSRF hidden field added to the page:
Use the performance data to further verify that everything is working as you expected.
Adding more protection
If you look further through the page source of the Qwerty application, you may also notice the following link:
This is a classic case of a GET request that can be exploited using CSRF. In this basic case study, we only protect POST requests of forms. However, if your application also uses actions on GET request, you can fairly easily amend the rule set to also cover GET requests.
This involves manipulating any URL parameters in the pages that are used for actions.
You can do this using the String Replacer rule, especially if your application uses ".jsp
" or ".do
" or ".aspx
" as URL identifiers for active content.
For example, you could replace ".jsp?
" in every page with ".jsp?CSRF=0123456789&
" and then check for the field on every URL that ends in ".jsp
" and has PARAMETER_NAMES (from HTTP Request Tracker Rule) not equal to blank. If you do that you will achieve the same result as the Insert Hidden Field rule does in this case study.
Additional CSRF notes
The above example is based on implementing the CSRF problem as a single rule set.
Last updated