如何实现 CSRF 保护
CSRF,或 跨站请求伪造,是一种恶意行为者诱骗用户在 Web 应用程序上执行他们不知情或不同意的操作的攻击类型。
这种攻击基于 Web 应用程序对用户浏览器(例如,会话 Cookie)的信任。这是一个 CSRF 攻击的真实示例:恶意行为者可以创建以下网站
1 2 3 4 5 6 7 8 9 10 11 12
<html>
<body>
<form action="https://example.com/settings/update-email" method="POST">
<input type="hidden" name="email" value="malicious-actor-address@some-domain.com"/>
</form>
<script>
document.forms[0].submit();
</script>
<!-- some content here to distract the user -->
</body>
</html>
如果您访问此网站(例如,通过点击某些电子邮件链接或某些社交网络帖子),并且您已经登录到 https://example.com
站点,则恶意行为者可以更改与您的帐户关联的电子邮件地址(有效地接管您的帐户),而您甚至没有意识到这一点。
防止 CSRF 攻击的有效方法是使用反 CSRF 令牌。这些是添加到表单中作为隐藏字段的唯一令牌。合法的服务器验证它们以确保请求来自预期的来源,而不是来自其他恶意网站。
安装
Symfony 提供了生成和验证反 CSRF 令牌所需的所有功能。在使用它们之前,请在您的项目中安装此软件包
1
$ composer require symfony/security-csrf
然后,使用 csrf_protection
选项启用/禁用 CSRF 保护(有关更多信息,请参阅 CSRF 配置参考)
1 2 3 4
# config/packages/framework.yaml
framework:
# ...
csrf_protection: ~
用于 CSRF 保护的令牌对于每个用户都是不同的,并且存储在会话中。这就是为什么当您呈现带有 CSRF 保护的表单时,会自动启动会话。
此外,这意味着您无法完全缓存包含 CSRF 保护表单的页面。作为替代方案,您可以
- 将表单嵌入到未缓存的 ESI 片段 中,并缓存页面的其余内容;
- 缓存整个页面,并通过未缓存的 AJAX 请求加载表单;
- 缓存整个页面,并使用 hinclude.js 通过未缓存的 AJAX 请求加载 CSRF 令牌,并将表单字段值替换为它。
Symfony 表单中的 CSRF 保护
Symfony 表单 默认包含 CSRF 令牌,Symfony 也会自动为您检查它们。因此,当使用 Symfony 表单时,您无需执行任何操作即可受到 CSRF 攻击的保护。
默认情况下,Symfony 在名为 _token
的隐藏字段中添加 CSRF 令牌,但这可以(1)全局自定义所有表单和(2)在每个表单的基础上自定义。全局来说,您可以在 framework.form
选项下配置它
1 2 3 4 5 6 7
# config/packages/framework.yaml
framework:
# ...
form:
csrf_protection:
enabled: true
field_name: 'custom_token_name'
在每个表单的基础上,您可以在每个表单的 setDefaults()
方法中配置 CSRF 保护
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// src/Form/TaskType.php
namespace App\Form;
// ...
use App\Entity\Task;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TaskType extends AbstractType
{
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Task::class,
// enable/disable CSRF protection for this form
'csrf_protection' => true,
// the name of the hidden HTML field that stores the token
'csrf_field_name' => '_token',
// an arbitrary string used to generate the value of the token
// using a different string for each form improves its security
'csrf_token_id' => 'task_item',
]);
}
// ...
}
您还可以通过创建自定义 表单主题 并使用 csrf_token
作为字段的前缀来自定义 CSRF 表单字段的呈现(例如,定义 {% block csrf_token_widget %} ... {% endblock %}
以自定义整个表单字段内容)。
手动生成和检查 CSRF 令牌
虽然 Symfony 表单默认提供自动 CSRF 保护,但您可能需要手动生成和检查 CSRF 令牌,例如在使用非 Symfony 表单组件管理的常规 HTML 表单时。
考虑创建一个 HTML 表单来允许删除项目。首先,使用 csrf_token() Twig 函数 在模板中生成 CSRF 令牌,并将其存储为隐藏表单字段
1 2 3 4 5 6
<form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
{# the argument of csrf_token() is an arbitrary string used to generate the token #}
<input type="hidden" name="token" value="{{ csrf_token('delete-item') }}">
<button type="submit">Delete item</button>
</form>
然后,在控制器操作中获取 CSRF 令牌的值,并使用 isCsrfTokenValid() 方法检查其有效性
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ...
public function delete(Request $request): Response
{
$submittedToken = $request->getPayload()->get('token');
// 'delete-item' is the same value used in the template to generate the token
if ($this->isCsrfTokenValid('delete-item', $submittedToken)) {
// ... do something, like deleting an object
}
}
或者,您可以使用控制器操作上的 IsCsrfTokenValid 属性
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
// ...
#[IsCsrfTokenValid('delete-item', tokenKey: 'token')]
public function delete(): Response
{
// ... do something, like deleting an object
}
假设您想要每个项目的 CSRF 令牌,因此在模板中您有类似以下内容
1 2 3 4 5 6
<form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
{# the argument of csrf_token() is a dynamic id string used to generate the token #}
<input type="hidden" name="token" value="{{ csrf_token('delete-item-' ~ post.id) }}">
<button type="submit">Delete item</button>
</form>
IsCsrfTokenValid 属性也接受评估为 ID 的 Expression 对象
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
// ...
#[IsCsrfTokenValid(new Expression('"delete-item-" ~ args["post"].getId()'), tokenKey: 'token')]
public function delete(Post $post): Response
{
// ... do something, like deleting an object
}
7.1
IsCsrfTokenValid 属性是在 Symfony 7.1 中引入的。
CSRF 令牌和压缩侧信道攻击
BREACH 和 CRIME 是针对使用 HTTP 压缩时 HTTPS 的安全漏洞。攻击者可以利用压缩泄漏的信息来恢复目标明文部分。为了缓解这些攻击并防止攻击者猜测 CSRF 令牌,随机掩码会添加到令牌前面并用于扰乱它。