Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EXTJS CSRF protection

I am trying to implement protection in one app against CSRF.

In PHP it is relatively simple to implement. I have many questions about how to do it with Extjs.

The EXTJS books I read do not address the subject and I can not find concrete guidance on this subject - with EXTJS - on the internet.

Some questions:

Using PHP, the token is sent to the EXTJS?

Do I have to create a hidden field in every form as in PHP?

Do I have to send to the server side the token in an Ext.Ajax.requestt? How to do this?

Some very simple code as a starting point:

class Token: https://www.youtube.com/watch?v=VflbINBabc4

<?php

 class Token {

 public static function generate() {
    $_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
 }

 public static function check($token) {
    if(isset($_SESSION['token']) && $token === $_SESSION['token']){
        unset($_SESSION['token']);
        return true;
    }
    return false;
 }
}
?>

Query

<?php

require('conect.php');

require_once('token.php');

$action = $_REQUEST['action'];

switch($action){

  case "create":{

        $records = $_POST['records'];
        $data = json_decode(stripslashes($records));

        if(isset($_POST['cars'], $_POST['token'])){

          $cars = $data->{'cars'};

           if(Token::check($_POST['token'])){

                 $sqlQuery = "INSERT INTO the_cars (cars) VALUES (?)";

                if($statement = $con->prepare($sqlQuery)){
                    $statement->bind_param("s", $cars);
                    $statement->execute();
                    $success= true;
                }else{
                    $erro = $con->error;
                    $success = false;
                }
           }else{
               //error
           }

            echo json_encode(array(
                "success" => $sucess,
                'errors'=> $erro
            ));

            $statement->close();
            $conexao->close();

            break;
      }
    }
?>

I would appreciate help to understand in detail how to implement this type of protection, using the code above as an example.

Thanks in advance.

Some useful posts:

CSRF prevention for AJAX call from extjs to Struts action

How to implement CSRFGuard in ExtJs AjaxRequest?

ExtJS Store SYNC with Spring Security ON

http://blog.gugl.org/archives/category/extjs

EDITED

One possibility I like is to send the token on every Ajax request: https://www.sencha.com/forum/showthread.php?134125

Mabe in Aplication.js. file

init: function () {

 Ext.require(["Ext.util.Cookies", "Ext.Ajax"], function(){
    // Add csrf token to every ajax request
    var token = Ext.util.Cookies.get('csrftoken');
    if(!token){
        Ext.Error.raise("Missing csrftoken cookie");
    } else {
        Ext.Ajax.defaultHeaders = Ext.apply(Ext.Ajax.defaultHeaders || {}, {
            'X-CSRFToken': token
        });
    }
 });
}

OR from Building Applications with EXT JS video publish by PACKT, but with node in serverside

var csrfToken = Ext.query('meta[name=csrf-token]')[0].getAttribute('content');
Ext.Ajax.defaultHeaders = ('X-CSRF-Token': csrfToken);
Ext.Ajax.extraParams = {'csrf': csrfToken};

I still have doubts about how to properly relate the server side (generate the token and do the respective check) with the client side.

EDITED

I have made several attempts in the last few days to run the CSRFProtector with php and EXTJS.

From the analysis carried out I was able to verify the following with Chrome Dev Tools:

If just at de beginning of the file index I add (and not in the other php files):

include_once __DIR__ .'csrfp/libs/csrf/csrfprotector.php';
csrfProtector::init()

I get on Chrome Dev Tools:

csrfprotector.js file is loaded

In loaded php files I have » Method: POST, Status 200, Type xhr, Initiator csrfprotector.js:259

I see that the data (in JSON format) and a token are sent and Request Headers as a Cookie with the same token

In the index.php file, in addition, the following is created, as expected:

  (...)
  <script type="text/javascript" 
  src="http://my_path/csrfp/js/csrfprotector.js"></script>
  <script type="text/javascript">
  window.onload = function() {
      csrfprotector_init();
  };
 </script>
 </body>
 </html>

No error is returned

When I add at the beginning of the php file (containing the query that will receive the data of the request, for example to create a record) the include_one and csrfProtector::init() the request is made, success is false and I get a status code 403 and the message 403 Access Forbidden by CSRFProtector!

If I add an echo 'Test 1', before csrfProtector::init (); and an echo 'Test 2' after, just first echo works. So it's not a problem in my php code but in validation with csrfprotector.

In Dev Tools you see that error 403 is triggered by mentioning the following script line: csrfprotector: 259. line 259 of that file is: return this.old_send (data);

I'm going to explore the possible incompatibility of csrfprotector with JSON.

If we were able to run the CSRFProtector with PHP and EXTJS (with JSON), it would be a solution that could make all the difference for many, as it is very easy to implement.

Example of the data format received on the server-side:

Array
(
    [action] => create
    [_dc] => 1505398990654
    [data] => {"id_cars":"id_1","cars":"test"},
)
like image 917
josei Avatar asked Sep 01 '17 20:09

josei


1 Answers

TL:DR

Considering you are using PHP, my main suggestion is to look at and use some existing solution like CSRF-Protector which is specifically designed to be used with PHP and should work with any client side framework including ExtJS. Its probably much better and safer than anything you can do by yourself.

NOTE: Project's wiki now contains two different pages with download links - this one contains links to outdated version and this one links to newest release. Make sure to download current release or clone repo!

Long Answer

I know your question is targeted on ExtJS solution specifically but it's too broad and missing some important details needed for good answer. What follows is some things you need to decide before you can even start thinking about "how to do this in code"...

Before i get into details, I strongly suggest to check following page for general considerations when designing CSRF protection: Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet

There are more ways how to handle CSRF protection. For the sake of simplicity I will discuss only the "Synchronizer (CSRF) Tokens" way described on the page mentioned above.

CSFR protection using "Synchronizer (CSRF) Tokens" always works like this:

  1. There is unprotected (in terms of CSRF) page\action\request which includes some form or action link which executes protected action (request). In your example it is the page which includes the ExtJs APP.MyApp class. This request also needs to generate CSRF token, store it in session (for later validation) and also include it somehow in generated response
  2. Protected action is requested while protection token is attached to the request
  3. Server-side action processing protected request extracts token from request and validates it against the value stored in session

Now there are more ways how to send generated CSRF tokens from server to client - meta, cookie, hidden field (all sort of mentioned in your question). The correct way to do it really depends on your app and desired protection level.

Main considerations are:

  • How the page initiating protected request is generated
  • What type of token are you using (per-session or per-request)

Token generation VS app\page lifestyle

As described in point 1. above, token is generated only when page initiating protected action is requested.

That's fine for multipage app because before protected action can be invoked, the page containing the form\link (and the token) must be generated. This means it's very easy to use per-request tokens and you can send the token in meta or form hidden field.

In the SPA app on the other side, where the initiating page is generated only once and protected action can be executed multiple times without full page refresh, your options are limited. Either you must use per-session token (see below) transferred via meta\header or you must use some more complicated mechanism to get new token using AJAX each time before you invoke protected action. For this case its better to use cookie as described in the link above it the "Double Submit Cookie" chapter

Token type

First you need to decide if your token will be per-session or per-request. Per-request tokens are generated for each request of the page initiating protected action and discarded after it is used (ie. protected action is executed after successful token validation). Can be stored in meta\header\hidden field. By definition, they are also not usable in SPA applications. There are also usability concerns like "back" button leading to false positive security event on the server if previous page is protected.

Per-session tokens are generated only once. This can lead to weaker security because it allows replay attacks but if your site is XSS safe (which it should be) its fine. Per-session tokens are easier to use in SPA applications. Can be transferred via meta\hidden field.

EDIT - CSRF-Protector VS ExtJs

It looks like current version of CSRF-Protector (v0.2.1) doesn't work with POST requests containing JSON payload (application/json) - see this issue in project's bug tracker. To work around this, make sure you always POST with Content-type: application/x-www-form-urlencoded.

For regular requests using Ext.Ajax.request do it by using params config instead of jsonData (fiddle)

Ext.Ajax.request({
            url: 'https://jsonplaceholder.typicode.com/posts/',
            method: 'POST',
            params: {
                "userId": 1,
                "id": 1,
                "title": "sunt",
                "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et"
            }
        })

For stores, make sure you configure writer with encode: true (see docs) like this (fiddle):

var store = new Ext.data.Store({
    model: 'MyApp.model.Post',
    storeId: 'postsStore',
    autoLoad: true,
    autoSync: true,
    loading: true,

    proxy: {
        type: 'rest',

        actionMethods: {
            create: 'POST',
            read: 'GET',
            update: 'PUT',
            destroy: 'DELETE'
        },

        api: {
            create: 'https://jsonplaceholder.typicode.com/posts',
            read: 'https://jsonplaceholder.typicode.com/posts',
            update: 'https://jsonplaceholder.typicode.com/posts',
            destroy: 'https://jsonplaceholder.typicode.com/posts'
        },

        reader: {
            type: 'json',
            rootProperty: 'data',
            totalProperty: 'total',
            successProperty: 'success'
        },

        writer: {
            type: 'json',
            writeAllFields: true,
            encode: true,
            rootProperty: 'data'
        }
    }
});
like image 96
Michal Levý Avatar answered Oct 16 '22 14:10

Michal Levý