9 min read

In this article by Y.E Liang, the author of JavaScript Security, we will cover cross-site forgery. This topic is not exactly new. In this article, we will go deeper into cross-site forgery and learn the various techniques of defending against it.

(For more resources related to this topic, see here.)

Introducing cross-site request forgery

Cross-site request forgery (CSRF) exploits the trust that a site has in a user’s browser. It is also defined as an attack that forces an end user to execute unwanted actions on a web application in which the user is currently authenticated.

Examples of CSRF

We will now take a look at a basic CSRF example:

  1. Go to the source code and change the directory. Run the following command:
    python xss_version.py
  2. Remember to start your MongoDB process as well.
  3. Next, open external.html found in templates, in another host, say http://localhost:8888. You can do this by starting the server, which can be done by running pyton xss_version.py –port=8888, and then visiting http://loaclhost:8888/todo_external. You will see the following screenshot:

    Adding a new to-do item

  4. Click on Add To Do, and fill in a new to-do item, as shown in the following screenshot:

    Adding a new to-do item and posting it

  5. Next, click on Submit. Going back to your to-do list app at http://localhost:8000/todo and refreshing it, you will see the new to-do item added to the database, as shown in the following screenshot:


    To-do item is added from an external app; this is dangerous!

  6. To attack the to-do list app, all we need to do is add a new item that contains a line of JavaScript, as shown in the following screenshot:


    Adding a new to do for the Python version

  7. Now, click on Submit. Then, go back to your to-do app at http://localhost:8000/todo, and you will see two subsequent alerts, as shown in the following screenshot:


    Successfully injected JavaScript part 1

  8. So here’s the first instance where CSRF happens:


    Successfully injected JavaScript part 2

    Take note that this can happen to the other backend written in other languages as well. Now go to your terminal, turn off the Python server backend, and change the directory to node/. Start the node server by issuing this command:

    node server.js

    This time around, the server is running at http://localhost:8080, so remember to change the $.post() endpoint to http://localhost:8080 instead of http://localhost:8000 in external.html, as shown in the following code:

       function addTodo() {
         var data = {
           text: $('#todo_title').val(),
           details:$('#todo_text').val()
         }
         // $.post('http://localhost:8000/api/todos', data, 
         function(result) {      $.post('http://localhost:8080/api/todos', data,
         function(result) {        var item = todoTemplate(result.text, result.details);        $('#todos').prepend(item);        $("#todo-form").slideUp();      })    }

    The line changed is found at addTodo(); the highlighted code is the correct endpoint for this section.

  9. Now, going back to external.html, add a new to-do item containing JavaScript, as shown in the following screenshot:


    Trying to inject JavaScript into a to-do app based on Node.js

  10. As usual, submit the item. Go to http://localhost:8080/api/ and refresh; you should see two alerts (or four alerts if you didn’t delete the previous ones). The first alert is as follows:


    Successfully injected JavaScript part 1

    The second alert is as follows:


    Successfully injected JavaScript part 1

Now that we have seen what can happen to our app if we suffered a CSRF attack, let’s think about how such attacks can happen.

Basically, such attacks can happen when our API endpoints (or URLs accepting the requests) are not protected at all. Attackers can exploit such vulnerabilities by simply observing which endpoints are used and attempt to exploit them by performing a basic HTTP POST operation to it.

Basic defense against CSRF attacks

If you are using modern frameworks or packages, the good news is that you can easily protect against such attacks by turning on or making use of CSRF protection. For example, for server.py, you can turn on xsrf_cookie by setting it to True, as shown in the following code:

class Application(tornado.web.Application):
   def __init__(self):
       handlers = [
           (r"/api/todos", Todos),
           (r"/todo", TodoApp)
 
       ]
       conn = pymongo.Connection("localhost")
       self.db = conn["todos"]
       settings = dict(
           xsrf_cookies=True,
           debug=True,
           template_path=os.path.join(os.path.dirname(__file__), 
           "templates"),            static_path=os.path.join(os.path.dirname(__file__),
           "static")        )        tornado.web.Application.__init__(self, handlers, **settings)

Note the highlighted line, where we set xsrf_cookies=True.

Have a look at the following code snippet:

var express   = require('express');
var bodyParser = require('body-parser');
var app       = express();
var session   = require('cookie-session');
var csrf   = require('csrf');
 
app.use(csrf());
app.use(bodyParser());

The highlighted lines are the new lines (compared to server.js) to add in CSRF protection.

Now that both backends are equipped with CSRF protection, you can try to make the same post from external.html. You will not be able to make any post from external.html. For example, you can open Chrome’s developer tool and go to Network. You will see the following:


POST forbidden

On the terminal, you will see a 403 error from our Python server, which is shown in the following screenshot:


POST forbidden from the server side

Other examples of CSRF

CSRF can also happen in many other ways. In this section, we’ll cover the other basic examples on how CSRF can happen.

CSRF using the <img> tags

This is a classic example. Consider the following instance:

<img src=http://yousite.com/delete?id=2 />

Should you load a site that contains this img tag, chances are that a piece of data may get deleted unknowingly.

Now that we have covered the basics of preventing CSRF attacks through the use of CSRF tokens, the next question you may have is: what if there are times when you need to expose an API to an external app? For example, Facebook’s Graph API, Twitter’s API, and so on, allow external apps not only to read, but also write data to their system.

How do we prevent malicious attacks in this situation? We’ll cover this and more in the next section.

Other forms of protection

Using CSRF tokens may be a convenient way to protect your app from CSRF attacks, but it can be a hassle at times. As mentioned in the previous section, what about the times when you need to expose an API to allow mobile access? Or, your app is growing so quickly that you want to accelerate that growth by creating a Graph API of your own.

How do you manage it then?

In this section, we will go quickly over the techniques for protection.

Creating your own app ID and app secret – OAuth-styled

Creating your own app ID and app secret is similar to what the major Internet companies are doing right now: we require developers to sign up for developing accounts and to attach an application ID and secret key for each of the apps.

Using this information, the developers will need to exchange OAuth credentials in order to make any API calls, as shown in the following screenshot:


Google requires developers to sign up, and it assigns the client ID

On the server end, all you need to do is look for the application ID and secret key; if it is not present, simply reject the request. Have a look at the following screenshot:


The same thing with Facebook; Facebook requires you to sign up, and it assigns app ID and app secret

Checking the Origin header

Simply put, you want to check where the request is coming from. This is a technique where you can check the Origin header.

The Origin header, in layman’s terms, refers to where the request is coming from. There are at least two use cases for the usage of the Origin header, which are as follows:

  • Assuming your endpoint is used internally (by your own web application) and checking whether the requests are indeed made from the same website, that is, your website.
  • If you are creating an endpoint for external use, such as those similar to Facebook’s Graph API, then you can make those developers register the website URL where they are going to use the API. If the website URL does not match with the one that is being registered, you can reject this request.

    Note that the Origin header can also be modified; for example, an attacker can provide a header that is modified.

Limiting the lifetime of the token

Assuming that you are generating your own tokens, you may also want to limit the lifetime of the token, for instance, making the token valid for only a certain time period if the user is logged in to your site. Similarly, your site can make this a requirement in order for the requests to be made; if the token does not exist, HTTP requests cannot be made.

Summary

In this article, we covered the basic forms of CSRF attacks and how to defend against it. Note that these security loopholes can come from both the frontend and server side.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here