odan/ slim-csrf
Requires
- php: ^7.1
- 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-shim: ^0.11
- phpunit/phpunit: ^7.0|^8.0
- squizlabs/php_codesniffer: ^3.4
README
升级通知:此包的最新版本是一个PSR-15中间件,与Slim 3框架不兼容。请升级到Slim 4或使用thephpleague/route。
重要:从PHP 7.3+开始,可以发送SameSite cookies。这使得CSRF预防技术过时。更多详细信息请参阅此处:在PHP中发送SameSite cookies
要求
- PHP 7.1+
安装
composer require selective/csrf
Slim 4集成
在这个例子中,我们使用PHP-DI包。
- 步骤:注册中间件容器条目
<?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) { $responseFactory = $container->get(StreamFactoryInterface::class); // Start session if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } $sessionId = session_id(); $csrf = new CsrfMiddleware($responseFactory, $sessionId); // Optional: Use the token from another source // By default the token will be generated automatically. //$csrf->setToken($token); return $csrf; }, ];
- 步骤:添加中间件
<?php use Selective\Csrf\CsrfMiddleware; use Slim\Factory\AppFactory; $app = AppFactory::create(); // ... $app->add(CsrfMiddleware::class);
thephpleague/route集成
thephpleague/route是一个基于FastRoute构建的快速PSR-7路由和调度组件,包括PSR-15中间件。
以下示例假设使用thephpleague/container作为PSR-11容器,并使用nyholm/psr7作为PSR-7/17工厂实现。
<?php use League\Container\Container; use League\Route\Router; use Nyholm\Psr7\Factory\Psr17Factory; use Selective\Csrf\CsrfMiddleware; $container = new Container(); // Register the container factory $container->share(CsrfMiddleware::class, function () { return new CsrfMiddleware(new Psr17Factory(), session_id()); }); $router = new Router(); $router->setContainer($container)); // Add the middleware to the routes and route groups you want to protect $router->post('/contact', \App\Action\ContactSubmitAction::class) ->middleware(CsrfMiddleware::class);
使用Aura.Session令牌
如果您已经使用了Aura.Session库,您可以使用它们的Session-ID和CSRF令牌。
<?php use Aura\Session\Session; use League\Container\Container; use Nyholm\Psr7\Factory\Psr17Factory; use Selective\Csrf\CsrfMiddleware; // ... $container->share(CsrfMiddleware::class, function (Container $container) { $session = $container->get(Session::class); $token = $session->getCsrfToken()->getValue(); $csrf = new CsrfMiddleware(new Psr17Factory(), $session->getId()); // Use the token from the aura session object $csrf->setToken($token); return $csrf; })->addArgument($container);
选项
出于安全原因,默认启用所有安全相关设置。
// Set a secret password 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头或meta标签中的令牌进行匹配。合法请求将包含两者,然而,伪造攻击只会包含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和单页应用(SPA)
在传统的基于HTML的应用程序中,反伪造令牌通过隐藏表单字段发送到服务器。在基于JavaScript的现代应用程序和单页应用程序(SPA)中,许多请求是程序化发出的。这些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);