Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing CSRF token from Node.js to Django

I'm working on a simple multiplayer word game app in Django (1.5). Following the example here, I'm using a separate Node.js server and Socket.io to manage the client connections.

My question breaks down into two parts:

  1. The tutorial above uses @csrf_exempt for the API view. As the POST does not come from the client, but from the Node.js server over localhost, what exactly am I exposed to by not using CSRF protection for this view?

  2. As I'm not sure of the above, I would like to use the CSRF protection. I have tried to extract the CSRF token from the cookie supplied by Django (as suggested by the docs) and send it along with the POST, but I still get a 403 response.

game_server.js:

io.configure(function () {
    io.set('authorization', function (data, accept) {
        if (data.headers.cookie) {
            data.cookie = cookie_reader.parse(data.headers.cookie);
            return accept(null, true);
        }
        return accept('error', false);
    });
    io.set('log level', 1);
});

io.sockets.on('connection', function (socket) {

    socket.on('check_word', function (data) {
        values = querystring.stringify({
            word: data,
            sessionid: socket.handshake.cookie['sessionid']
        });

        var options = {
            host: 'localhost',
            port: 8000,
            path: '/node/check_word',
            method: 'POST',
            headers: {
                'X-CSRFToken': socket.handshake.cookie['csrftoken'],
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': values.length
            }
        };

        var req = http.request(options, function (res) {
            res.setEncoding('utf8');

            res.on('data', function (message) {
                if (message) {
                    console.log(message);
                }
            });
        });

        req.write(values);
        req.end();
    });
});

game.html (script portion only):

(function ($) {
  var socket = io.connect('localhost', { port: 4000 });

  socket.on('connect', function () {
    console.log("connected");
  });

  word_el = $('#word-input');

  word_el.keypress(function (event) {
    if (event.keyCode === 13) {
      // Enter key pressed
      var word = word_el.attr('value');
      if (word) {
        socket.emit('check_word', word, function (data) {
          console.log(data);
        });
      }

      word_el.attr('value', '');
    }
  });
})(jQuery);

views.py:

@ensure_csrf_cookie
def check_word(request):
    return HttpResponse("MATCH:" + request.POST.get('word'))

Any insight would be greatly appreciated!

like image 901
ACEfanatic02 Avatar asked Nov 02 '22 18:11

ACEfanatic02


1 Answers

After a fair bit of research and experimentation, I've solved the problem.

My findings:

  1. In this particular case, CSRF doesn't expose me to any meaningful attacks. In theory it opens a route for cheating in the game, but that's a lot of difficulty (requiring fabrication of session id and targeting a game currently in progress) for zero reward. However, in other applications such as chat, a CSRF vulnerability here allows for someone to impersonate another user, which is a more important concern. And so we dig deeper...

  2. My original attempt to solve the problem via AJAX headers was a mistake. For one, the request is not actually coming over AJAX. (request.is_ajax() returns False within the view.) Secondly, the error page received from Django cites CSRF cookie not set as the reason for failure.

All of which builds up to the solution:

var options = {
    // snip...
    headers: {
        'Cookie': 'csrftoken=' + socket.handshake.cookie['csrftoken'],
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': values.length
    }
};

Add the proper 'Cookie' header, and the request succeeds.

like image 193
ACEfanatic02 Avatar answered Nov 09 '22 14:11

ACEfanatic02