opctim / symfony-csp-bundle
这个包帮助您在symfony应用中使用CSP头部来正确地保护您的应用程序。
Requires
- php: >=7.4.33
- ext-openssl: *
- symfony/event-dispatcher: ^5.4 || ^6.4 || ^7.0
- symfony/framework-bundle: ^5.4 || ^6.4 || ^7.0
Requires (Dev)
- phpunit/phpunit: ^8.5
- symfony/yaml: ^5.4
- twig/twig: ^3.9
Suggests
- twig/twig: For using the csp_nonce() method in templates.
README
曾经与CSP头部抗争过吗?我也是。以前配置CSP头部总是很痛苦。
但设置CSP头部指令比以往任何时候都更重要!如果您曾经遇到过不同的跟踪脚本,您可能也注意到了有多少额外的第三方脚本被懒加载。这可能会导致恶意JavaScript被加载到您的页面上,这可能会造成灾难性的后果,尤其是在构建支付网关时。
它甚至可以帮助您在unsafe-inline
指令(您应该避免使用)
的情况下添加动态的Nonce标记。
要求
- PHP >= 7.4.33,已安装OpenSSL扩展
- Symfony >= 5.4
安装
composer require opctim/symfony-csp-bundle
配置
在您的config/
目录中,添加或编辑opctim_csp_bundle.yaml
# config/packages/opctim_csp_bundle.yaml opctim_csp_bundle: always_add: [] report: url: null route: null route_params: [] chance: 100 directives: default-src: - "'self'" - 'data:' - '*.example.com' base-uri: - "'self'" object-src: - "'none'" script-src: - "'self'" - "nonce(payment-app)" # For more info, see "Dynamic nonce tokens" section below! - '*.example.com' img-src: - "'self'" - '*.example.com' style-src: - "'self'" - "'unsafe-inline'" connect-src: - '*.example.com' font-src: - '*.example.com' frame-src: - "'self'" - '*.example.com' frame-ancestors: - "'self'" - '*.example.com'
所以
default-src: - "'self'" - 'data:' - '*.example.com'
变成了
Content-Security-Policy: default-src 'self' data: *.example.com;
always_add选项
如名称所示,此选项将指定的来源添加到所有指令中。这对于与when@dev
一起使用非常有用。
# config/packages/opctim_csp_bundle.yaml opctim_csp_bundle: always_add: [] directives: default-src: - "'self'" - 'data:' - '*.example.com' base-uri: - "'self'" object-src: - "'none'" script-src: - "'self'" - "nonce(payment-app)" # For more info, see "Dynamic nonce tokens" section below! - '*.example.com' when@dev: opctim_csp_bundle: always_add: - '*.example.local'
重要:如果您将'none'
作为第一个和唯一的指令添加,则此指令将跳过always_add
功能。此功能是在1.1.4
中添加的。
您还可以使用when@dev
有条件地向特定指令添加来源。
# config/packages/opctim_csp_bundle.yaml opctim_csp_bundle: always_add: [] directives: default-src: - "'self'" - 'data:' - '*.example.com' script-src: - "'self'" - '*.example.com' when@dev: opctim_csp_bundle: directives: connect-src: - 'some.external.additional.host.com'
报告选项
此包为您提供了一种简单配置CSP报告功能的方法,该功能告诉浏览器如果您的CSP配置拒绝特定资源,则向您的后端报告。浏览器中目前有两种实现 - report-uri和report-to
- https://mdn.org.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri
- https://mdn.org.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to
因此,根据MDN文档,此包添加了report-uri指令和Reporting-Endpoint头部,以支持未来的新浏览器。
此包提供了一个向后兼容的实现,应该被所有浏览器支持。
# config/packages/opctim_csp_bundle.yaml opctim_csp_bundle: always_add: [] report: url: null route: my_awesome_controller_action route_params: [] chance: 100 directives: default-src: - "'self'" - 'data:' - '*.example.com'
url
-可选
您可以将外部URL传递到这里,浏览器应该向其报告。route
-可选
如果您想使用您的控制器动作接收报告。这将使用UrlGenerator为您生成一个绝对URL。route_params
-可选
如果您正在使用route
参数,您可以在其中传递额外的路由参数。chance
-可选
此字段的单位是百分比。它指定了将报告指令添加到响应中的概率应该有多高。
以下是一些解释更改选项的伪代码
if (random_int(0, 99) < $chance) { $someService->addReportHeaders(); }
这意味着,对于100%的概率,它将每次都运行。根据您应用程序的流量,建议设置大约5-10%的概率,以避免被CSP日志消息淹没。
动态nonce标记
动态nonce标记可以非常有用,允许在您的Twig模板中允许特定的内联脚本标记,而无需忽略安全担忧,例如通过不添加或硬编码它们;)
配置语法
nonce(<handle>)
示例
在 opctim_csp_bundle.yaml
中
opctim_csp_bundle: always_add: [] directives: default-src: - "'self'" - 'data:' - '*.example.com' script-src: - "'self'" - '*.example.com' - 'nonce(my-inline-script)'
根据请求,nonce(my-inline-script)
将被转换为例如 nonce-25d2ec8bb6
,并随后出现在响应的CSP头部中。
然后,在您的twig模板中,您可以简单地使用此bundle提供的 csp_nonce('my-inline-script')
函数。
<script type="text/javascript" nonce="{{ csp_nonce('my-inline-script') }}"> alert('This works!'); </script>
渲染结果
<script type="text/javascript" nonce="25d2ec8bb6"> alert('This works!'); </script>
挂钩到CSP头部生成
此bundle的一个关键特性是动态nonce实现。bundle挂钩到Symfony事件系统,并为您生成新的nonce令牌 - 每个请求都会生成!
在请求时,bundle准备将CSP头部指令写入响应头。在这里,解析了来自 opctim_csp_bundle.yaml
的 nonce()
表达式。
bundle会将此值添加到以下三个头部中,以实现跨浏览器的兼容性
- 内容安全策略
- X-内容安全策略
- X-WebKit-CSP
如果您想在写入响应之前修改CSP头部,可以通过订阅 opctim_csp_bundle.add_csp_header
事件来挂钩到生成过程。
<?php # src/EventSubscriber/ModifyCspHeaderEventSubscriber.php declare(strict_types=1); namespace App\EventSubscriber; use Opctim\CspBundle\Event\AddCspHeaderEvent; use Opctim\CspBundle\Service\CspHeaderBuilderService; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class ModifyCspHeaderEventSubscriber implements EventSubscriberInterface { public function __construct( private CspHeaderBuilderService $cspHeaderBuilderService ) {} public static function getSubscribedEvents(): array { return [ AddCspHeaderEvent::NAME => 'modifyCspHeader' ]; } public function modifyCspHeader(AddCspHeaderEvent $event): void { // Use the request if you like $request = $event->getRequest(); $cspHeader = $this->cspHeaderBuilderService->build( [ // alwaysAdd options ...$this->cspHeaderBuilderService->getAlwaysAdd(), // Merge the existing ones... 'some-conditional-always-to-be-added-origin' ], [ // directive options ...$this->cspHeaderBuilderService->getDirectives(), // Merge the existing ones... 'script-src' => [ // Override something here 'some-conditional-origin' ] ] ); $cspHeader = str_replace('foo', 'bar', $cspHeader); // Maybe some string transformations here... $event->setCspHeaderValue($cspHeader); // Set the newly crafted csp header. // On response, the bundle will add your new CSP header! } }
测试
测试位于 tests/
文件夹中,可以使用 vendor/bin/phpunit
运行。
composer install vendor/bin/phpunit