yiisoft/csrf
GitHub: yiisoft/csrf
一款基于 PSR-15 的 CSRF 防护中间件,提供 Synchronizer 和 HMAC 双令牌算法及令牌掩码机制,用于保障 Web 表单与 API 的请求安全。
Stars: 27 | Forks: 9
Yii CSRF Protection Library
[](https://packagist.org/packages/yiisoft/csrf) [](https://packagist.org/packages/yiisoft/csrf) [](https://github.com/yiisoft/csrf/actions/workflows/build.yml) [](https://codecov.io/gh/yiisoft/csrf) [](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/csrf/master) [](https://github.com/yiisoft/csrf/actions?query=workflow%3A%22static+analysis%22) [](https://shepherd.dev/github/yiisoft/csrf) 该包提供了用于 CSRF 防护的 [PSR-15](https://www.php-fig.org/psr/psr-15/) 中间件: - 它开箱即用地支持两种算法: - 具有可自定义 token 生成和存储的 Synchronizer CSRF token。默认情况下,它使用随机数据和 session。 - 具有可自定义身份生成的 HMAC token。默认使用 session。 - 它能够对 CSRF token 字符串进行掩码处理,从而使 [BREACH 攻击](https://breachattack.com/) 无法实施。 - 它支持通过自定义 header 为 AJAX/SPA 后端 API 提供 CSRF 防护。 ## 要求 - PHP 7.4 - 8.5。 ## 安装 该包可以使用 [Composer](https://getcomposer.org) 安装: ``` composer require yiisoft/csrf ``` ## 常规用法 为了启用 CSRF 防护,您需要将 `CsrfTokenMiddleware` 添加到您的主中间件堆栈中。 在 Yii 中,这是通过配置 [`MiddlewareDispatcher`](https://github.com/yiisoft/middleware-dispatcher) 来完成的: ``` $middlewareDispatcher = $injector->make(MiddlewareDispatcher::class); $middlewareDispatcher = $middlewareDispatcher->withMiddlewares([ ErrorCatcher::class, SessionMiddleware::class, CsrfTokenMiddleware::class, // <-- add this Router::class, ]); ``` 或者在 [DI 容器](https://github.com/yiisoft/di) 中定义 `MiddlewareDispatcher` 配置: ``` // config/web/di/application.php return [ MiddlewareDispatcher::class => [ 'withMiddlewares()' => [[ ErrorCatcher::class, SessionMiddleware::class, CsrfTokenMiddleware::class, // <-- add this Router::class, ]] ], ]; ``` 默认情况下,CSRF token 是从 `_csrf` 请求主体参数或 `X-CSRF-Token` header 中获取的。 您可以使用 `CsrfTokenInterface` 以字符串形式访问当前有效的 token: ``` /** @var Yiisoft\Csrf\CsrfTokenInterface $csrfToken */ $csrf = $csrfToken->getValue(); ``` 如果 token 未通过验证,将返回 `422 Unprocessable Entity` 响应。 您可以通过实现自己的请求 handler 来更改此行为: ``` use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Yiisoft\Csrf\CsrfTokenMiddleware; /** * @var Psr\Http\Message\ResponseFactoryInterface $responseFactory * @var Yiisoft\Csrf\CsrfTokenInterface $csrfToken */ $failureHandler = new class ($responseFactory) implements RequestHandlerInterface { private ResponseFactoryInterface $responseFactory; public function __construct(ResponseFactoryInterface $responseFactory) { $this->responseFactory = $responseFactory; } public function handle(ServerRequestInterface $request): ResponseInterface { $response = $this->responseFactory->createResponse(400); $response ->getBody() ->write('Bad request.'); return $response; } }; $middleware = new CsrfTokenMiddleware($responseFactory, $csrfToken, $failureHandler); ``` 默认情况下,`CsrfTokenMiddleware` 将 `GET`、`HEAD`、`OPTIONS` 方法视为安全操作,并且不执行 CSRF 验证。您可以按如下方式更改此行为: ``` use Yiisoft\Csrf\CsrfTokenMiddleware; use Yiisoft\Http\Method; $csrfTokenMiddleware = $container->get(CsrfTokenMiddleware::class); // Returns a new instance with the specified list of safe methods. $csrfTokenMiddleware = $csrfTokenMiddleware->withSafeMethods([Method::OPTIONS]); // Returns a new instance with the specified header name. $csrfTokenMiddleware = $csrfTokenMiddleware->withHeaderName('X-CSRF-PROTECTION'); ``` 或者在 [DI 容器](https://github.com/yiisoft/di) 中定义 `CsrfTokenMiddleware` 配置: ``` // config/web/di/csrf-token.php use Yiisoft\Csrf\CsrfTokenMiddleware; use Yiisoft\Http\Method; return [ CsrfTokenMiddleware::class => [ 'withSafeMethods()' => [[Method::OPTIONS]], 'withHeaderName()' => ['X-CSRF-PROTECTION'], ], ]; ``` ## CSRF token 如果 Yii 框架与 config 插件一起使用,该包会自动[配置](./config/di-web.php) 为使用 synchronizer token 和 masked 装饰器。您可以根据需要更改它。 对于敏感的匿名表单使用 synchronizer token;对于仅限已认证用户的表单,当提交的 token 可能保持几分钟的有效期时,请使用 HMAC token。 ``` flowchart TD A{Anonymous forms to protect?} A -- Yes --> S[Synchronizer] A -- No --> B{Old or repeated submits must fail?} B -- Yes --> S B -- No --> C{Per-environment secret key?} C -- No --> S C -- Yes --> D{Token replay within lifetime OK?} D -- No --> S D -- Yes --> H[HMAC] ``` 详细对比: | 因素 | Synchronizer | HMAC | |--------|--------------|------| | 每次请求的 I/O | Session 读取和写入 | 无 token 存储 I/O | | 基于文件的 session GC | 可能会扫描 session 文件 | 不被 CSRF token 存储触发 | | Token 存储增长 | 取决于 session 存储 | 无需存储 | | Token 吊销 | 可通过移除存储的 token 实现 | 在 token 过期前无法实现 | | 生命周期内的重放 | 通过存储策略阻止 | 在 token 过期前可能发生 | 要将 token 切换为 HMAC: ``` use Yiisoft\Csrf\CsrfTokenInterface; use Yiisoft\Csrf\Hmac\HmacCsrfToken; use Yiisoft\Csrf\MaskedCsrfToken; use Yiisoft\Definitions\Reference; return [ CsrfTokenInterface::class => [ 'class' => MaskedCsrfToken::class, '__construct()' => [ 'token' => Reference::to(HmacCsrfToken::class), ], ], ]; ``` ### Synchronizer CSRF token Synchronizer CSRF token 是一种有状态的 CSRF token,它是一个唯一的随机字符串。它被保存在仅可供当前登录用户访问的持久化存储 中。相同的 token 会被添加到表单中。当提交表单时, 来自表单的 token 会与存储的 token 进行比较。 `SynchronizerCsrfToken` 需要实现以下接口: - `CsrfTokenGeneratorInterface`,用于生成新的 CSRF token; - `CsrfTokenStorageInterface`,用于在请求之间持久化 token。 该包提供了 `RandomCsrfTokenGenerator`(用于生成随机 token)和 `SessionCsrfTokenStorage`(用于在用户 session 中在请求之间持久化 token)。 要了解更多关于 synchronizer token 模式的信息, [请查看 OWASP CSRF 备忘单](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern)。 ### 基于 HMAC 的 token 基于 HMAC 的 token 是一种无状态的 CSRF token,不需要任何存储。该 token 是由 session ID 和 用于防止重放攻击的 timestamp 生成的哈希值。该 token 会被添加到表单中。当提交表单时,我们会从当前 session ID 和原始 token 中的 timestamp 重新生成 该 token。如果两个哈希值匹配,我们会检查 该 timestamp 是否小于 token 的有效期。 `HmacCsrfToken` 需要实现 `CsrfTokenIdentityGeneratorInterface` 以生成身份。 该包提供了 `SessionCsrfTokenIdentityGenerator`,它使用 session ID,从而将 session 作为 token 作用域。 通过 `HmacCsrfToken` 构造函数设置的参数有: - `$secretKey` — 用于生成哈希的共享密钥; - `$algorithm` — 用于消息认证的哈希算法。推荐使用 `sha256`、`sha384` 或 `sha512`; - `$lifetime` — token 有效期的秒数。 当通过 config 插件使用 HMAC 时,通过参数配置这些构造函数参数: ``` return [ 'yiisoft/csrf' => [ 'hmacToken' => [ 'secretKey' => (string) getenv('YII_CSRF_SECRET_KEY'), 'algorithm' => 'sha256', 'lifetime' => 300, ], ], ]; ``` 要了解更多关于基于 HMAC 的 token 模式的信息 [请查看 OWASP CSRF 备忘单](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#hmac-based-token-pattern)。 ### Stub CSRF token `StubCsrfToken` 仅存储并返回 token 字符串。它不执行任何额外的验证。 在单元测试期间模拟 CSRF token 行为或在临时解决方案中提供 占位符功能时,此实现非常有用。 ### Masked CSRF token `MaskedCsrfToken` 是 `CsrfTokenInterface` 的装饰器,用于对 token 字符串应用掩码。 它使得 [BREACH 攻击](https://breachattack.com/) 无法实施,因此可以安全地在 HTML 中使用 token,随后将其作为隐藏表单字段或通过 JavaScript 异步请求 传递给下一个请求。 建议始终使用此装饰器。 ## AJAX/SPA 后端 API 的 CSRF 防护 如果您使用 cookie 对 AJAX/SPA 进行身份验证,那么您的后端 API 确实需要 CSRF 防护。 ### 使用自定义请求 header 在此模式中,AJAX/SPA 前端会向需要 CSRF 防护的 API 请求追加自定义 header。此方法不需要 token。这种防御依赖于 CORS 预检机制,该机制会发送一个 `OPTIONS` 请求,以验证是否符合目标服务器的 CORS 规则。根据同源策略安全模型,所有现代浏览器都会将带有自定义 header 的请求指定为“需要进行预检”。当 API 需要自定义 header 时,您就知道如果请求来自浏览器,那它一定经过了预检。 该 header 可以是任意的键值对,只要它不与现有的 header 冲突即可。空值也是可以接受的。 ``` X-CSRF-HEADER=1 ``` 在处理请求时,API 会检查此 header 是否存在。如果该 header 不存在,后端会将该请求作为潜在的伪造请求予以拒绝。使用自定义 header 可以拒绝浏览器未指定为“需要进行预检”的[简单请求](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests),并禁止它们被发送到任何来源。 为了启用 CSRF 防护,您需要将 `CsrfHeaderMiddleware` 添加到 [`MiddlewareDispatcher`](https://github.com/yiisoft/middleware-dispatcher) 配置中: ``` $middlewareDispatcher = $injector->make(MiddlewareDispatcher::class); $middlewareDispatcher = $middlewareDispatcher->withMiddlewares([ ErrorCatcher::class, CsrfHeaderMiddleware::class, // <-- add this Router::class, ]); ``` 或者在 [DI 容器](https://github.com/yiisoft/di) 中: ``` // config/web/di/application.php return [ MiddlewareDispatcher::class => [ 'withMiddlewares()' => [[ ErrorCatcher::class, CsrfHeaderMiddleware::class, // <-- add this Router::class, ]] ], ]; ``` 或者将 `CsrfHeaderMiddleware` 添加到必须受保护的路由的 [router](https://github.com/yiisoft/router) 配置中: ``` // config/web/di/router.php return [ RouteCollectionInterface::class => static function (RouteCollectorInterface $collector) use ($config) { $collector ->middleware(CsrfHeaderMiddleware::class) // <-- add this ->addGroup(Group::create(null)->routes($routes)); return new RouteCollection($collector); }, ]; ``` 默认情况下,`CsrfHeaderMiddleware` 仅将 `GET`、`HEAD`、`POST` 方法视为不安全操作。使用其他 HTTP 方法的请求会触发 CORS 预检,不需要进行 CSRF header 验证。您可以按如下方式更改此行为: ``` use Yiisoft\Csrf\CsrfHeaderMiddleware; use Yiisoft\Http\Method; $csrfHeaderMiddleware = $container->get(CsrfHeaderMiddleware::class); // Returns a new instance with the specified list of unsafe methods. $csrfHeaderMiddleware = $csrfHeaderMiddleware->withUnsafeMethods([Method::POST]); // Returns a new instance with the specified header name. $csrfHeaderMiddleware = $csrfHeaderMiddleware->withHeaderName('X-CSRF-PROTECTION'); ``` 或者在 [DI 容器](https://github.com/yiisoft/di) 中定义 `CsrfHeaderMiddleware` 配置: ``` // config/web/di/csrf-header.php use Yiisoft\Csrf\CsrfHeaderMiddleware; use Yiisoft\Http\Method; return [ CsrfHeaderMiddleware::class => [ 'withUnsafeMethods()' => [[Method::POST]], 'withHeaderName()' => ['X-CSRF-PROTECTION'], ], ]; ``` 使用自定义请求 header 进行 CSRF 防护基于 CORS 协议。因此,您**必须**配置 CORS 模块以允许或拒绝跨域访问后端 API。 ### 保护同源请求 在此场景中: - AJAX/SPA 前端和 API 后端具有相同的源。 - 拒绝访问 API 服务器的跨域请求。 - 必须限制简单的 CORS 请求。 #### 配置 CORS 模块 - 对 CORS 预检请求的响应**不得**包含 CORS header。 - 对实际请求的响应**不得**包含 CORS header。 #### 配置中间件堆栈 将 `CsrfHeaderMiddleware` 添加到 [`MiddlewareDispatcher`](https://github.com/yiisoft/middleware-dispatcher) 配置中: ``` $middlewareDispatcher = $injector->make(MiddlewareDispatcher::class); $middlewareDispatcher = $middlewareDispatcher->withMiddlewares([ ErrorCatcher::class, CsrfHeaderMiddleware::class, // <-- add this Router::class, ]); ``` 或者添加到必须受保护的路由的 [router](https://github.com/yiisoft/router) 配置中: ``` $collector = $container->get(RouteCollectorInterface::class); $collector->addGroup( Group::create('/api') ->middleware(CsrfHeaderMiddleware::class) // <-- add this ->routes($routes) ); ``` #### 配置前端请求 在前端,向 `GET`、`HEAD`、`POST` 请求添加在 `CsrfHeaderMiddleware` 中定义的自定义 header,其值可以为空或随机值。 ``` let response = fetch('https://example.com/api/whoami', { headers: { "X-CSRF-HEADER": crypto.randomUUID() } }); ``` ### 保护来自特定来源列表的请求 在此场景中: - AJAX/SPA 前端和 API 后端具有不同的源。 - 仅允许特定来源列表的跨域请求访问 API 服务器。 - 必须限制简单的 CORS 请求。 #### 配置 CORS 模块 - 对 CORS 预检请求的成功响应**必须**包含适当的 CORS header。 - 对实际请求的响应**必须**包含适当的 CORS header。 - CORS header `Access-Control-Allow-Origin` 的值**必须**包含来自预定义列表的源。 ``` // assuming frontend origin is https://example.com and backend origin is https://api.example.com Access-Control-Allow-Origin: https://example.com ``` #### 配置中间件堆栈 将 `CsrfHeaderMiddleware` 添加到 [`MiddlewareDispatcher`](https://github.com/yiisoft/middleware-dispatcher) 配置中: ``` $middlewareDispatcher = $injector->make(MiddlewareDispatcher::class); $middlewareDispatcher = $middlewareDispatcher->withMiddlewares([ ErrorCatcher::class, CsrfHeaderMiddleware::class, // <-- add this Router::class, ]); ``` 或者添加到必须受保护的路由的 [router](https://github.com/yiisoft/router) 配置中: ``` $collector = $container->get(RouteCollectorInterface::class); $collector->addGroup( Group::create('/api') ->middleware(CsrfHeaderMiddleware::class) // <-- add this ->routes($routes) ); ``` #### 配置前端请求 在前端,向 `GET`、`HEAD`、`POST` 请求添加在 `CsrfHeaderMiddleware` 中定义的自定义 header,其值可以为空或随机值。 ``` let response = fetch('https://api.example.com/whoami', { headers: { "X-CSRF-HEADER": crypto.randomUUID() } }); ``` ### 保护从任何来源传递的请求 在此场景中: - AJAX/SPA 前端和 API 后端具有不同的源。 - 允许来自任何来源的跨域请求访问 API 服务器。 - 所有请求都被视为不安全,并且**必须**使用 CSRF token 进行 CSRF 防护。 #### 配置 CORS 模块 - 对 CORS 预检请求的成功响应**必须**包含适当的 CORS header。 - 对实际请求的响应**必须**包含适当的 CORS header。 - CORS header `Access-Control-Allow-Origin` 的值与请求中的 `Origin` header 值相同。 ``` $frontendOrigin = $request->getOrigin(); Access-Control-Allow-Origin: $frontendOrigin ``` #### 配置中间件堆栈 默认情况下,`CsrfTokenMiddleware` 将 `GET`、`HEAD`、`OPTIONS` 方法视为安全操作,不执行 CSRF 验证。 在基于 JavaScript 的应用中,请求是通过编程方式发出的;因此,为了增强应用保护,可以仅将 `OPTIONS` 方法视为安全操作,并且不需要附加 CSRF token header。 配置 `CsrfTokenMiddleware` 的安全方法: ``` use Yiisoft\Csrf\CsrfTokenMiddleware; use Yiisoft\Http\Method; $csrfTokenMiddleware = $container->get(CsrfTokenMiddleware::class); $csrfTokenMiddleware = $csrfTokenMiddleware->withSafeMethods([Method::OPTIONS]); ``` 或者在 [DI 容器](https://github.com/yiisoft/di) 中: ``` // config/web/di/csrf-token.php use Yiisoft\Csrf\CsrfTokenMiddleware; use Yiisoft\Http\Method; return [ CsrfTokenMiddleware::class => [ 'withSafeMethods()' => [[Method::OPTIONS]], ], ]; ``` 将 `CsrfTokenMiddleware` 添加到 [`MiddlewareDispatcher`](https://github.com/yiisoft/middleware-dispatcher) 配置中: ``` $middlewareDispatcher = $injector->make(MiddlewareDispatcher::class); $middlewareDispatcher = $middlewareDispatcher->withMiddlewares([ ErrorCatcher::class, SessionMiddleware::class, CsrfTokenMiddleware::class, // <-- add this Router::class, ]); ``` 或者添加到必须受保护的路由的 [router](https://github.com/yiisoft/router) 配置中: ``` $collector = $container->get(RouteCollectorInterface::class); $collector->addGroup( Group::create('/api') ->middleware(CsrfTokenMiddleware::class) // <-- add this ->routes($routes) ); ``` #### 配置路由 在 [router](https://github.com/yiisoft/router) 配置中创建一个路由,用于从前端应用获取 CSRF token。 ``` use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Yiisoft\Http\Header; use Yiisoft\Http\Method; use Yiisoft\Router\Route; Route::options('/csrf-token') ->action(static function ( ResponseFactoryInterface $responseFactory, CsrfTokenInterface $token ): ResponseInterface { $tokenValue = $token->getValue(); $response = $responseFactory->createResponse() ->withHeader(Header::ALLOW, Method::OPTIONS) ->withHeader('X-CSRF-TOKEN', $tokenValue); $response->getBody()->write($tokenValue); return $response; }), ``` #### 配置前端请求 在前端,首先向配置的 endpoint 发出请求并获取 CSRF token,以便在后续请求中使用它。 ``` let response = await fetch('https://api.example.com/csrf-token'); let csrfToken = await response.text(); // OR let csrfToken = response.headers.get('X-CSRF-TOKEN'); ``` 向所有请求添加在 `CsrfTokenMiddleware` 中定义的自定义 header,并附带获取的 CSRF token 值。 ``` let response = fetch('https://api.example.com/whoami', { headers: { "X-CSRF-TOKEN": csrfToken } }); ``` ## 文档 - [内部机制](docs/internals.md) 如果您需要帮助或有疑问,[Yii 论坛](https://forum.yiiframework.com/c/yii-3-0/63) 是个不错的去处。 您还可以查看其他 [Yii 社区资源](https://www.yiiframework.com/community)。 ## 许可证 Yii CSRF 防护库是免费软件。它根据 BSD 许可证的条款发布。 请参阅 [`LICENSE`](./LICENSE.md) 获取更多信息。 由 [Yii Software](https://www.yiiframework.com/) 维护。 ## 支持该项目 [](https://opencollective/yiisoft) ## 关注更新 [](https://www.yiiframework.com/) [](https://twitter.com/yiiframework) [](https://t.me/yii3en) [](https://www.facebook.com/groups/yiitalk) [](https://yiiframework.com/go/slack)
标签:CSRF, ffuf, OpenVAS, PHP, PSR-15, Syscall, Web开发, 中间件, 安全防护