riimu / kit-csrf

安全且简单的 CSRF 库,可抵御时间攻击和 BREACH 攻击

v2.4.0 2015-08-22 06:11 UTC

This package is auto-updated.

Last update: 2024-09-19 19:34:39 UTC


README

CSRF 是一个用于防止 [跨站请求伪造] (https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29) 攻击的 PHP 库。CSRF 攻击利用了已认证的用户,将他们引导到恶意网站,该网站向目标网站发送精心设计的请求以修改网站内容。攻击利用已认证用户的浏览器发送请求,绕过任何认证。这个库通过要求在每次 POST、PUT 和 DELETE 请求中包含 CSRF 令牌来防止这些攻击。这些令牌对攻击者来说是未知的,这阻止了他们发送恶意请求。

这个库支持使用 cookies 或 sessions 存储CSRF 令牌。令牌也可以通过 POST 请求中的隐藏表单字段或使用 HTTP 头部来提交,这使得在 AJAX 请求中传递令牌更加容易。

为了提供针对 CSRF 令牌不同形式的攻击的额外安全性,这个库使用恒定时间字符串比较来防止时间攻击,并在每次请求中生成随机加密令牌来防止 BREACH 攻击。除此之外,所有令牌都是使用安全的随机字节生成器生成的。

可以使用 Apigen 生成的 API 文档,可以在以下地址在线阅读:http://kit.riimu.net/api/csrf/

Build Status Code Coverage Scrutinizer Code Quality

要求

为了使用此库,必须满足以下要求

安装

可以通过使用 Composer 来安装此库。为了做到这一点,您必须下载最新版本的 Composer 并运行 require 命令将此库作为依赖项添加到您的项目中。完成这两个任务的简单方法是,在您的终端中运行以下两个命令

php -r "readfile('https://getcomposer.org.cn/installer');" | php
php composer.phar require "riimu/kit-csrf:2.*"

如果您已经在系统上安装了 Composer 并知道如何使用它,您也可以通过将此库添加为依赖项到您的 composer.json 文件并运行 composer install 命令来安装此库。以下是一个您的 composer.json 文件可能的样子

{
    "require": {
        "riimu/kit-csrf": "2.*"
    }
}

通过 Composer 安装此库后,您可以通过包含 Composer 在安装过程中生成的 vendor/autoload.php 文件来加载库。

手动安装

您还可以不使用 Composer 手动安装此库。为了做到这一点,您必须下载最新的发行版并从存档中提取 src 文件夹到您的项目文件夹中。要加载库,您只需包含存档中提供的 src/autoload.php 文件。

请注意,如果您手动安装此库,您还必须自行安装依赖项。通过 Composer 安装库也会为您安装依赖项。

用法

这个库的目的是让安全尽可能方便。你只需要使用CSRFHandler类提供的两种方法。应该在每次请求的开始就调用validateRequest()方法。此方法仅验证POST、PUT和DELETE请求,因此你可以在每个请求上安全地调用它。可以使用getToken()方法通过名为csrf_token的隐藏字段检索应包含在每个提交表单中的令牌。

如果提交的令牌与存储在cookie或会话中的秘密令牌不匹配,则validateRequest()方法将发送HTTP 400(错误请求)响应头并终止脚本执行。这不应影响您网站的正常使用,但将阻止针对您网站的任何CSRF攻击尝试。

以下是一个简单的网页示例,其中有一个可以提交的表单

<?php

require 'vendor/autoload.php';
$csrf = new \Riimu\Kit\CSRF\CSRFHandler();
$csrf->validateRequest();
$token = $csrf->getToken();

?>
<!DOCTYPE html>
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Simple Form</title>
 </head>
 <body>
<?php

if (!empty($_POST['my_name'])) {
    printf("  <p>Hello <strong>%s</strong>!</p>" . PHP_EOL, htmlspecialchars($_POST['my_name'], ENT_QUOTES | ENT_HTML5, 'UTF-8'));
}

?>
  <form method="post"><div>
   <input type="hidden" name="csrf_token" value="<?=htmlspecialchars($token, ENT_QUOTES | ENT_HTML5, 'UTF-8')?>" />
   What is your name?
   <input type="text" name="my_name" />
   <input type="submit" />
  </div></form>
 </body>
</html>

使用会话

默认情况下,库会将秘密令牌保存到cookie中。如果您希望将令牌保存到会话中,可以在初始化CSRFHandler时将构造函数参数设置为false。例如

<?php

session_start();
require 'vendor/autoload.php';
$csrf = new \Riimu\Kit\CSRF\CSRFHandler(false);

处理无效令牌

如果您希望对请求发送无效csrf令牌时的行为有更多控制,可以将传递给validateRequest()的参数设置为true。这将导致方法抛出InvalidCSRFTokenException异常,而不是终止脚本。例如

<?php

require 'vendor/autoload.php';
$csrf = new \Riimu\Kit\CSRF\CSRFHandler();

try {
    $csrf->validateRequest(true);
} catch (\Riimu\Kit\CSRF\InvalidCSRFTokenException $ex) {
    header('HTTP/1.0 400 Bad Request');
    exit('Bad CSRF Token!');
}

使用令牌头

请注意,如果您正在构建网站或使用ajax请求发送POST、PUT或DELETE请求,您也可以通过头文件提供csrf令牌。

要使用头文件提供令牌,只需包含一个名为X-CSRF-Token的头文件,其中包含您将包含在csrf_token表单字段中的相同值。

使用nonce

nonce是一个只能使用一次的令牌。将CSRF令牌转换为nonce可以提供针对重放攻击的保护。然而,需要注意的是,针对此类攻击的最佳防御措施是使用安全的HTTPS连接。然而,如果您无法使用加密连接,则可能可以通过[使用nonce](http://blog.ircmaxell.com/2013/02/preventing-csrf-attacks.html)来防止这些攻击。

此库通过使用NonceValidator类提供了一种实现nonce的方法。此类与CSRFHandler完全相同,除了它只接受由getToken()生成的每个令牌一次。即使攻击者可以监听连接,他们也无法重发HTTP请求,因为令牌只能使用一次。

您可以使用NonceValidator,就像您使用CSRFHandler一样,例如

<?php

require 'vendor/autoload.php';

session_start();
$csrf = new \Riimu\Kit\CSRF\NonceValidator();
$csrf->validateRequest();
$token = $csrf->getToken();

请注意,NonceValidator始终使用会话来存储CSRF令牌。除此之外,它还会存储已经使用过且不能再使用的令牌。如果您有一个依赖于大量表单提交的网站,这个无效令牌数组可能会变得相当大。要清除此数组,只需使用regenerateToken()重新生成令牌。例如

<?php

require 'vendor/autoload.php';

session_start();
$csrf = new \Riimu\Kit\CSRF\NonceValidator();
$csrf->validateRequest();

if ($csrf->getNonceCount() > 100) {
    $csrf->regenerateToken();
}

$token = $csrf->getToken();

手动使用

如果您希望对令牌验证有更多控制,此库提供了几个方法,允许您手动管理库的多个方面。为了方便起见,CSRFHandler提供了以下方法

  • isValidatedRequest()告诉当前请求是否为POST、PUT或DELETE请求,这些请求应该被验证。

  • validateRequest($throw = false)验证请求并在令牌无效时终止脚本或抛出异常。仅在对POST、PUT和DELETE请求进行验证时才会验证令牌。

  • validateRequestToken()验证请求中发送的令牌。如果令牌存在并且与秘密令牌匹配,则返回true。

  • validateToken($token) 可以用于手动验证令牌。传递给方法的方法应该是由 getToken() 返回的令牌。

  • getToken() 返回一个有效的 base64 编码的令牌。

  • regenerateToken() 重新生成秘密 CSRF 令牌并使之前由 getToken() 返回的所有令牌失效。

  • getTrueToken() 返回用于验证用户提交的令牌的存储秘密 CSRF 令牌。

  • getRequestToken() 返回请求中发送的令牌。

保护您的网站

即使这个库不能防止 CSRF 攻击,如果您未能正确使用令牌。每个请求都得到适当的验证,并且每个提交的表单都发送了令牌,这一点非常重要。然而,还有一些陷阱您应该注意。

如果您不使用非ces,在某些罕见的情况下,也可能使用 [会话固定] (https://www.owasp.org/index.php/Session_fixation) 攻击来确定已认证用户使用的 CSRF 令牌。即使会话 ID 在登录时被重新生成,攻击者仍然可能利用已知的 CSRF 令牌。为了防止这种情况,建议在通过调用 regenerateToken() 进行认证时重新生成令牌。

为了创建一个不受 CSRF 攻击影响的网站,您还必须记住,只有 POST、PUT 和 DELETE 请求应该改变网站的状态。CSRF 令牌永远不应该在 GET 参数中提供,因为这可以通过各种不同的攻击泄露。因此,GET 请求永远不应该影响状态。例如,允许用户通过简单的 GET 请求被删除会使您的网站容易受到 CSRF 攻击。

然而,如果您确实想创建一个安全的网站,那么您还必须只使用加密连接,即您必须使用 HTTPS。这是防止 中间人攻击 的唯一有效措施,但它也有助于防止重放攻击。

最后,请记住 CSRF 令牌仅保护您免受外部请求的攻击。它们对 跨站脚本 攻击没有保护作用。如果攻击者能够在您的网站上运行 JavaScript,CSRF 令牌就不会提供额外的保护。通过 XSS 攻击,攻击者始终能够找到 CSRF 令牌。您网站的安全性与最薄弱的环节一样脆弱。

CSRF 令牌的结构

这个库生成的所有令牌都是随机的 32 字节字符串。这些字符串是通过使用 SecureRandom 库生成的,以确保它们是由安全的随机源生成的。但是,getToken() 返回的令牌长度是前者的两倍以上。这是因为它们是包含使用秘密令牌作为盐的令牌散列版本的 base64 编码字符串。

为了防止 BREACH 攻击,getToken() 返回的每个令牌都是不同的,因为静态令牌可以被用来破解 HTTPS 连接所使用的加密。为了实现这一点,返回的令牌实际上由一个随机生成的令牌和该令牌的加密版本组成,该版本使用 HMAC-SHA256 加密,秘密令牌作为密钥。(这也使得无法逆向操作以找到秘密令牌)。这使得每个令牌都不同,但在调用 regenerateToken() 之前仍然有效。因此,返回的解码字符串的实际长度是 64 字节。

请注意,每次调用 getToken() 方法时都会生成一个新的随机令牌。因此,该方法返回的每个字符串都是不同的。如果您网页上有大量表单,使用 SingleToken 类可能更有效率,该类只加载一次令牌,并且可以将其转换为字符串。

版权信息

本库的版权所有为2014 - 2015年,归Riikka Kalliomäki所有。

有关许可证和复制信息,请参阅LICENSE文件。

本库的实现基于Go库nosurf中的想法,由Justinas Stankevicius提供。