EXTJS CSRF保护

EXTJS CSRF protection

我正在尝试在一个应用程序中实施针对 CSRF 的保护。

在PHP中实现起来比较简单。我有很多关于如何使用 Extjs 的问题。

我读过的 EXTJS 书籍没有涉及这个主题,我在互联网上找不到关于这个主题的具体指导 - 使用 EXTJS。

一些问题:

使用PHP,令牌发送到EXTJS?

我是否必须像 PHP 那样在每个表单中创建一个隐藏字段?

我是否必须在 Ext.Ajax.requestt 中向服务器端发送令牌?如何做到这一点?

一些非常简单的代码作为起点:

class 代币: 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;
 }
}
?>

查询

<?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;
      }
    }
?>

我将不胜感激,以上面的代码为例,详细了解如何实施此类保护。

提前致谢。

一些有用的帖子:

How to implement CSRFGuard in ExtJs AjaxRequest?

ExtJS Store SYNC with Spring Security ON

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

已编辑

我喜欢的一种可能性是在每个 Ajax 请求上发送令牌:https://www.sencha.com/forum/showthread.php?134125

Mabe 在 Aplication.js。文件

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
        });
    }
 });
}

或来自 使用 EXT JS 构建应用程序 由 PACKT 发布的视频,但节点在服务器端

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

我仍然对如何正确关联服务器端(生成令牌并进行相应的检查)与客户端有疑问。

已编辑

在过去的几天里,我多次尝试 运行 使用 php 和 EXTJS 的 CSRFProtector。

根据所进行的分析,我能够使用 Chrome 开发工具验证以下内容:

如果刚好在我添加的文件索引的开头(而不是在其他 php 文件中):

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

我上Chrome开发工具:

csrfprotector.js 文件已加载

在已加载的 php 文件中 » 方法:POST、状态 200、类型 xhr、启动器 csrfprotector.js:259

我看到数据(JSON 格式)和一个令牌已发送,请求 Headers 作为具有相同令牌的 Cookie

在 index.php 文件中,另外,按预期创建了以下内容:

  (...)
  <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>

return没有错误

当我在 php 文件(包含将接收请求数据的查询,例如创建记录)的开头添加 include_one 和 csrfProtector::init() 发出请求,成功为假,我收到状态代码 403 和消息 403 Access Forbidden by CSRFProtector!

如果我在csrfProtector::init()之前加一个echo'Test 1';和 echo 'Test 2' 之后,只有第一个 echo 起作用。所以这在我的 php 代码中不是问题,但在使用 csrfprotector 进行验证时是问题。

在 Dev Tools 中,您看到错误 403 是通过提及以下脚本行触发的:csrfprotector: 259。 该文件的第 259 行是:returnthis.old_send(数据);

我将探讨 csrfprotector 可能与 JSON 不兼容的问题。

如果我们能够 运行 使用 PHP 和 EXTJS(使用 JSON)的 CSRFProtector,这将是一个可以对许多人产生重大影响的解决方案,因为它很容易实现。

server-side 上收到的数据格式示例:

Array
(
    [action] => create
    [_dc] => 1505398990654
    [data] => {"id_cars":"id_1","cars":"test"},
)

TL:DR

考虑到您正在使用 PHP,我的主要建议是查看并使用一些现有的解决方案,例如 CSRF-Protector,它专门设计用于 PHP,并且应该与任何客户端框架,包括 ExtJS。它可能比您自己做的任何事情都更好、更安全。

注意:项目的 wiki 现在包含两个不同的页面,下载 links - this one contains links to outdated version and this one links 到 最新版本 。确保下载当前版本或克隆存储库!

长答案

我知道您的问题是专门针对 ExtJS 解决方案的,但它过于宽泛并且缺少一些获得良好答案所需的重要细节。接下来是您在开始考虑“如何在代码中执行此操作”之前需要决定的一些事情...

在我进入细节之前,我强烈建议查看以下页面以了解设计 CSRF 保护时的一般注意事项:Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet

处理 CSRF 保护的方法有很多。为了简单起见,我将仅讨论上述页面中描述的“同步器 (CSRF) 令牌”方式。

使用“同步器 (CSRF) 令牌”的 CSFR 保护总是这样工作的:

  1. 存在未受保护的(就 CSRF 而言)page\action\request,其中包括执行受保护操作(请求)的某种形式或操作 link。在您的示例中,它是包含 ExtJs APP.MyApp class 的页面。此请求还需要生成 CSRF 令牌,将其存储在会话中(供以后验证),并以某种方式将其包含在生成的响应中
  2. 保护令牌附加到请求时请求保护操作
  3. 服务器端操作处理受保护的请求从请求中提取令牌并根据会话中存储的值对其进行验证

现在有更多方法可以将生成的 CSRF 令牌从服务器发送到客户端 - 元数据、cookie、隐藏字段(您的问题中提到的所有类型)。正确的做法实际上取决于您的应用和所需的保护级别。

主要考虑因素是:

  • 发起受保护请求的页面是如何生成的
  • 您使用什么类型的令牌(每个会话或每个请求)

代币生成 VS app\page 生活方式

如上文第 1 点所述,只有在请求页面启动受保护操作时才会生成令牌。

这对于多页面应用程序来说很好,因为在调用受保护的操作之前,必须生成包含 form\link(和令牌)的页面。这意味着使用每个请求令牌非常容易,您可以在元数据或表单隐藏字段中发送令牌。

在另一边的 SPA 应用程序中,启动页面只生成一次,受保护的操作可以执行多次而无需刷新整个页面,您的选择是有限的。您必须使用通过 meta\header 传输的每会话令牌(见下文),或者您必须使用一些更复杂的机制在每次调用受保护操作之前使用 AJAX 获取新令牌。对于这种情况,最好使用上面link“双重提交 Cookie”一章

中所述的 cookie

令牌类型

首先您需要决定您的令牌是按会话还是按请求。为页面发起受保护操作的每个请求生成每个请求令牌,并在使用后丢弃(即受保护操作在令牌验证成功后执行)。可以存储在 meta\header\hidden 字段中。根据定义,它们也不能用于 SPA 应用程序。 如果上一页受到保护,还存在可用性问题,例如“后退”按钮会导致服务器上出现误报安全事件。

每会话令牌只生成一次。 这可能会导致安全性较弱,因为它允许重放攻击,但如果您的网站是 XSS 安全的(它应该是安全的),那就没问题了。每会话令牌更易于在 SPA 应用程序中使用。可以通过meta\hidden字段传输。

编辑 - CSRF-Protector VS ExtJs

看起来当前版本的 CSRF-Protector (v0.2.1) 不适用于 POST 包含 JSON 有效负载 (application/json) 的请求 - 请参阅 this issue 在项目的错误跟踪器中。要解决此问题,请确保始终 POST 和 Content-type: application/x-www-form-urlencoded.

对于使用 Ext.Ajax.request 的常规请求,请使用 params 配置而不是 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"
            }
        })

对于商店,请确保使用 encode: true 配置编写器(参见 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'
        }
    }
});