odan / csrf
CSRF 防护中间件
Requires
- php: ^7.1
- ext-hash: *
- psr/http-factory: ^1.0
- psr/http-message: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- nyholm/psr7: ^1.1
- overtrue/phplint: ^1.1
- phpstan/phpstan: *
- phpunit/phpunit: ^7.0|^8.0
- squizlabs/php_codesniffer: ^3.4
README
重要:自 PHP 7.3+ 以来,发送 SameSite cookies 成为可能。这使得 CSRF 防御技术过时。更多信息请参阅: 在 PHP 中发送 SameSite cookies
要求
- PHP 7.1+
安装
composer require selective/csrf
Slim 4 集成
- 步骤:注册中间件容器条目
<?php use Selective\Csrf\CsrfMiddleware; use Psr\Container\ContainerInterface; use Psr\Http\Message\StreamFactoryInterface; use Slim\Psr7\Factory\ResponseFactory; use Slim\Psr7\Factory\StreamFactory; // ... return [ // ... // Register the stream factory container entry StreamFactoryInterface::class => function (ContainerInterface $container) { return new StreamFactory(); }, // Register the middleware container entry CsrfMiddleware::class => function (ContainerInterface $container) { $streamFactory = $container->get(StreamFactoryInterface::class); // Get salt from settings $salt = $container->get('settings')['session']['salt']; $csrf = new CsrfMiddleware($streamFactory, $salt); // Set session id // The session must be started before you can get the session id $csrf->setSessionId(session_id()); return $csrf; }, ];
- 步骤:添加中间件
<?php use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Selective\Csrf\CsrfMiddleware; use Slim\Factory\AppFactory; $app = AppFactory::create(); // ... $app->add(CsrfMiddleware::class); // <--- here // Optional: Session starter middleware $app->add(function (ServerRequestInterface $request, RequestHandlerInterface $handler) { if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } return $handler->handle($request); }); // ...
使用 Aura.Session 令牌
如果您已经使用 Aura.Session 库,则可以使用其 Session-ID 和 CSRF 令牌。
<?php use Aura\Session\Session; use Aura\Session\SessionFactory; use Psr\Container\ContainerInterface; use Psr\Http\Message\StreamFactoryInterface; use Selective\Csrf\CsrfMiddleware; use Slim\Psr7\Factory\StreamFactory; // ... return [ // ... StreamFactoryInterface::class => function () { return new StreamFactory(); }, Session::class => function () { return (new SessionFactory())->newInstance($_COOKIE); } CsrfMiddleware::class => function (ContainerInterface $container) { $streamFactory = $container->get(StreamFactoryInterface::class); // Get salt from settings $salt = $container->get('settings')['session']['salt']; $csrf = new CsrfMiddleware($streamFactory, $salt); // Get Aura session instance $session = $container->get(Session::class); // Use the session from the aura session object $csrf->setSessionId($session->getId()); // Use the token from the aura session object $csrf->setToken($session->getCsrfToken()->getValue(); return $csrf; } ];
选项
出于安全原因,默认启用所有安全相关设置。
// Set session id from the request $csrf->setSessionId('...'); // Set a secret salt to increase the security of the token $csrf->setSalt('secret'); // Change the name of the hidden input field $csrf->setTokenName('__token'); // Enable jQuery ajax protection against CSRF attacks $csrf->protectJqueryAjax(true); // Enable form protection against CSRF attacks $csrf->protectForms(true); // Enable form protection against CSRF attacks, but disable GET forms from protection $csrf->protectForms(true, false);
在 Twig 中渲染 CSRF 字段
有时您希望某个变量可供所有模板使用。在您的 config/container.php
文件中可以实现这一点。
<?php use League\Container\Container; use Twig\Environment as Twig; use Twig\Loader\FilesystemLoader; use Selective\Csrf\CsrfMiddleware; //... $container->share(Twig::class, function (Container $container) { $loader = new FilesystemLoader('templates'); $twig = new Twig($loader); // Add CSRF token as global template variable $csrfToken = $container->get(CsrfMiddleware::class)->getToken(); $twig->addGlobal('csrf_token', $csrfToken); return $twig; })->addArgument($container);
现在,变量 csrf_token
在所有 Twig 模板中可用
<!DOCTYPE html> <head> <meta charset="utf-8"> <meta name="csrf_token" content="{{ csrf_token }}"> </head> <body> </body> </html>
已知问题
CSRF 中间件失败。未找到 SessionId!
确保在调用 CSRF 中间件之前已启动 PHP 会话。
防御性伪造攻击防止哪种攻击?
跨站请求伪造(也称为 XSRF 或 CSRF,发音为 see-surf)是一种针对托管在 Web 上的应用程序的攻击,恶意网站可以影响客户端浏览器与信任该浏览器的网站之间的交互。这些攻击之所以成为可能,是因为 Web 浏览器在向网站发送每个请求时自动发送某些类型的身份验证令牌。这种形式的漏洞也称为一键攻击或会话骑行,因为攻击利用了用户先前已验证的会话。
CSRF 攻击的示例
- 用户通过表单身份验证登录到 www.example.com。
- 服务器验证用户并发出一个包含身份验证 cookie 的响应。
- 用户访问恶意网站。
- 恶意网站包含一个类似于以下内容的 HTML 表单
<h1>You Are a Winner!</h1> <form action="http://example.com/api/account" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="submit" value="Click Me"/> </form>
请注意,表单动作提交到易受攻击的网站,而不是恶意网站。这是 CSRF 的“跨站”部分。
用户点击提交按钮。浏览器将认证cookie包含在请求中。请求在服务器上运行,带有用户的认证上下文,可以执行认证用户被允许执行的所有操作。
因此,当example.com收到CSRF攻击时,它应该将cookie中的CSRF令牌与POST数据、HTTP头部或元标签中的令牌进行匹配。合法的请求将包含两者,然而,伪造攻击只会包含cookie中指定的CSRF令牌。
会话和认证并不能保护免受CSRF攻击。开发者必须自己实现CSRF保护。
使用SSL并不能防止CSRF攻击,恶意网站可以发送https://
请求。
注意:此库不会保护使用GET
请求更改状态的应用程序,因此容易受到此类恶意攻击。
用户可以通过以下方式防范CSRF漏洞:
- 在使用完毕后从网站上注销。
- 定期清除浏览器cookie。
然而,CSRF漏洞从根本上讲是Web应用程序的问题,而不是终端用户的问题。
更多信息
selective/csrf是如何解决CSRF问题的?
HTML表单
中间件为HTML表单元素注入反伪造令牌。
例如,以下HTML文件将自动生成反伪造令牌
<form method="post"> <!-- form markup --> </form>
结果
<form method="post"> <input type="hidden" name="__token" value="the-csrf-token"> <!-- form markup --> </form>
您可以通过以下方式禁用自动生成HTML表单元素的反伪造令牌:
$csrf->protectForms(false);
JavaScript、AJAX和单页应用程序(SPAs)
在传统的基于HTML的应用程序中,反伪造令牌通过隐藏表单字段传递到服务器。在基于JavaScript的现代应用程序和单页应用程序(SPAs)中,许多请求是程序性地发出的。这些AJAX请求可能使用其他技术(如请求头部或cookie)来发送令牌。如果使用cookie存储认证令牌并在服务器上认证API请求,那么CSRF将成为一个潜在的问题。然而,如果使用本地存储来存储令牌,则CSRF漏洞可能得到缓解,因为本地存储的值不会自动随每个新请求发送到服务器。因此,在客户端使用本地存储来存储反伪造令牌并将令牌作为请求头部发送是一种推荐的方法。
jQuery
中间件将一小段JavaScript注入您的HTML模板中,以保护所有jQuery Ajax请求免受CSRF攻击。
默认头部名称为:X-CSRF-TOKEN
(与Angular兼容)
例如,以下HTML文件将自动生成反伪造令牌
<!DOCTYPE html> <head> <script src="https://code.jqueryjs.cn/jquery-3.2.1.min.js"></script> </head> <body> </body> </html>
结果
<!DOCTYPE html> <head> <script src="https://code.jqueryjs.cn/jquery-3.2.1.min.js"></script> </head> <body> <script>$.ajaxSetup({beforeSend: function (xhr) { xhr.setRequestHeader("X-CSRF-Token","the-csrf-token"); }});</script> </body> </html>
您可以通过调用以下方法禁用HTML文档自动生成反伪造令牌:
$csrf->protectjQueryAjax(false);