Bảo mật và vận hành
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Cầu nối này gửi Hypertext Markup Language (HTML) của bạn qua một ranh giới mạng đến một browser engine. Trang này ghi lại các biện pháp kiểm soát bảo vệ ranh giới đó, lấy mã nguồn làm cơ sở xác thực. Khi một biện pháp kiểm soát trích dẫn một tiêu chuẩn, đó chính là trích dẫn được khai báo trong docblock của mã. Trang này diễn đạt lại các khẳng định trong mã; nó không tái dựng câu chữ chuẩn tắc.
Mô hình mối đe dọa
Phần tiêu đề “Mô hình mối đe dọa”Các docblock của gói nêu rõ những mối đe dọa mà gói phòng vệ:
- XSS-to-PDF — Cross-site scripting (XSS) thông qua markup độc hại chạy trong quá trình kết xuất Portable Document Format (PDF).
- SSRF — Server-side request forgery (SSRF) do markup hoặc một Uniform Resource Locator (URL) đích gửi yêu cầu đến địa chỉ nội bộ.
- Cạn kiệt tài nguyên — Đầu vào quá lớn hoặc bom giải nén.
- DNS rebinding — DNS rebinding (Domain Name System), khi một hostname vượt qua bước xác thực rồi sau đó phân giải thành một địa chỉ riêng tư vào thời điểm kết nối.
- Chặn TLS trên đường truyền — Chặn Transport Layer Security (TLS) trên đường truyền bằng một chứng chỉ bị thay thế trên đường đến Worker.
Mỗi mối đe dọa đều có một biện pháp kiểm soát cụ thể, có thể kiểm thử, được trình bày bên dưới.
Kiểm soát đầu vào (trước khi yêu cầu rời khỏi PHP)
Phần tiêu đề “Kiểm soát đầu vào (trước khi yêu cầu rời khỏi PHP)”CloudflareSecurityPolicy::validate() chạy trước khi bất kỳ yêu cầu nào được dựng:
| Biện pháp kiểm soát | Hành vi | Nguồn giới hạn |
|---|---|---|
| Giới hạn kích thước | Từ chối HTML lớn hơn maxHtmlSize | CloudflareRendererConfig, mặc định 5000000 byte |
| Bộ bảo vệ bom giải nén Base64 | Ước tính kích thước sau giải mã của mọi URI data:…;base64,…; từ chối các giá trị bằng hoặc vượt ngưỡng trần | MAX_DATA_URI_BYTES = 13631488 |
| Cấm meta-refresh | Từ chối mọi <meta http-equiv="refresh">, không phân biệt chữ hoa chữ thường | regex trong CloudflareSecurityPolicy |
Một vi phạm sẽ ném RuntimeException kèm thông báo nêu rõ giá trị vi phạm và giới hạn. Lệnh cấm meta-refresh tồn tại vì một chỉ thị refresh có thể khởi động điều hướng bên trong trang mà Worker kết xuất — một vector SSRF nằm trong nội dung, không phải trong URL.
Chính sách bảo mật HTML từ nextpdf/core (HtmlSecurityPolicyInterface, mặc định DefaultHtmlSecurityPolicy) chạy ở lớp phân tích cú pháp và bổ trợ cho các bước kiểm tra ở lớp transport nêu trên. Hãy lấy chính sách này bằng getHtmlSecurityPolicy(). Có thể chèn một chính sách tùy chỉnh thông qua constructor.
Kiểm soát đích đến (SSRF và DNS rebinding)
Phần tiêu đề “Kiểm soát đích đến (SSRF và DNS rebinding)”CloudflareSecurityPolicy::validateWorkerUrl():
- Từ chối một URL không phân tích được hoặc thiếu scheme/host (
Invalid Worker URL). - Từ chối mọi scheme khác ngoài HTTPS (
Worker URL must use HTTPS). - Với host là IP literal, từ chối các dải riêng tư hoặc dải dành riêng bằng
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGEcủa PHP. Trên thực tế, điều này từ chối không gian riêng tư RFC 1918, loopback và các địa chỉ link-local RFC 3927. Các bài kiểm thử bao phủ rõ ràng việc từ chối192.168.x,127.0.0.1và169.254.x. Phần mở rộng filter của PHP quyết định một địa chỉ thuộc dải nào; gói này không gắn quyết định đó với một điều khoản cụ thể. RFC 1918 và RFC 3927 được nêu tên ở đây theo nghĩa mô tả, như những định nghĩa quen thuộc cho các dải đó. - Với hostname, phân giải tất cả bản ghi A và AAAA bằng
dns_get_record()(không phảigethostbyname(), vốn chỉ trả về câu trả lời đầu tiên) và từ chối host nếu bất kỳ địa chỉ nào được phân giải là riêng tư hoặc dành riêng.
Việc phân giải toàn bộ bản ghi là có chủ đích. Docblock của lớp ghi lại rằng đây là cơ chế phòng vệ trước host trả về nhiều bản ghi, trong đó việc tra cứu chỉ một bản ghi có thể chọn địa chỉ công khai, còn kết nối sau đó lại chọn địa chỉ riêng tư. Điều này khớp với OWASP SSRF Prevention Cheat Sheet: phân giải cả câu trả lời A lẫn AAAA cho domain và áp dụng bước kiểm tra địa chỉ không công khai cho toàn bộ tập kết quả.
validateWorkerUrl() trả về tập IP đã được thẩm định. Ngay trước khi gửi, bộ kết xuất gọi assertPinsStillValid(). Lệnh gọi này phân giải lại host và từ chối mọi IP mới xuất hiện (Worker URL DNS answer changed since validation — possible DNS rebinding attack). Điều này đóng cửa sổ time-of-check / time-of-use giữa thời điểm xác thực và thời điểm kết nối.
Kiểm soát transport (PinnedCurlTransport)
Phần tiêu đề “Kiểm soát transport (PinnedCurlTransport)”Khi có một tập IP đã thẩm định hoặc một tập ghim Subject Public Key Info (SPKI) và có cung cấp một ResponseFactory PHP Standards Recommendation 17 (PSR-17), bộ kết xuất dùng Transport\PinnedCurlTransport thay cho client PHP Standards Recommendation 18 (PSR-18) được tiêm vào. Transport thực thi các biện pháp kiểm soát này ở lớp cURL handle:
- Ghim DNS —
CURLOPT_RESOLVEgắn host:port với tập IP đã thẩm định, nên libcurl không tự tra cứu tại thời điểm kết nối. Sự gắn kết này khiến bước kiểm tra DNS ở userland áp dụng cho chính kết nối thực tế; nếu không có nó, libcurl có thể phân giải ra một địa chỉ khác. - Ghim khóa công khai TLS —
CURLOPT_PINNEDPUBLICKEYđược đặt từ tập ghim hợp nhất. Điều này tuân theo RFC 7469 §2.6: kết nối có ghim được chấp nhận khi tập dấu vân tay SPKI mà máy chủ trình ra giao với tập ghim đã cấu hình, và lỗi xác thực ghim là không thể khôi phục. Các chuỗi ghim được chuẩn hóa từ dạngsha256/<base64>sang dạngsha256//<base64>của cURL; ghim sai định dạng sẽ némInvalidSpkiPinException. - Bật xác minh TLS —
CURLOPT_SSL_VERIFYPEER => true,CURLOPT_SSL_VERIFYHOST => 2. - Không chuyển hướng tự động —
CURLOPT_FOLLOWLOCATION => false,CURLOPT_MAXREDIRS => 0. Phản hồi 3xx được đưa lên lớp chính sách thay vì để libcurl đi theo đến một host chưa được thẩm định. Docblock của lớp nêu rằng đây là chủ ý, để các chuyển hướng được xác thực lại thay vì bị âm thầm đi theo. - Thời gian chờ cứng —
CURLOPT_TIMEOUTđược đặt từrenderTimeout(mặc định30giây).
Lỗi cURL hoặc body không phải chuỗi sẽ ném CloudflareRenderException kèm số lỗi và thông báo của cURL.
Hướng dẫn vận hành ghim
Phần tiêu đề “Hướng dẫn vận hành ghim”Cấu hình mang theo pinnedPublicKeys và một backupPublicKeys riêng. RFC 7469 §2.5 mô tả ghim dự phòng là dấu vân tay cho cặp khóa thứ hai chưa triển khai, được giữ ngoại tuyến, và xem đó là đường khôi phục chính khi xác thực ghim thất bại ngoài ý muốn. Hãy giữ ít nhất một ghim dự phòng để việc xoay vòng chứng chỉ không làm hỏng endpoint. Trường riêng này cho phép bạn kiểm chứng một lần xoay vòng một cách độc lập. Về mặt vận hành:
- Hãy ghim SPKI của chứng chỉ lá hoặc của một chứng chỉ trung gian mà bạn kiểm soát được việc xoay vòng.
- Luôn cấu hình ghim dự phòng cho chứng chỉ kế tiếp trước khi xoay vòng.
- Một tập ghim rỗng sẽ vô hiệu hóa việc ghim; chỉ dùng cách đó với một chuỗi chứng chỉ ổn định, đã biết. Việc ghim là tùy chọn được bật qua cấu hình.
Xác thực và xử lý bí mật
Phần tiêu đề “Xác thực và xử lý bí mật”- Yêu cầu Worker mang theo
Authorization: Bearer <apiToken>.apiTokenlà#[SensitiveParameter], nên các stack trace sẽ ẩn giá trị này. Phép thăm dò khả năng tiếp cận gửi cùng header bearer đó trên mộtHEADHypertext Transfer Protocol (HTTP). - Các access key Cloudflare R2 (
accessKeyId,secretAccessKey) là#[SensitiveParameter]và chỉ được dùng để dẫn xuất khóa ký Amazon Web Services (AWS) Signature V4. ApiKeyValidatorso sánh các khóa bằnghash_equals()(an toàn về thời gian) và hỗ trợ lưu trữ khóa đã băm Secure Hash Algorithm 256 (SHA-256) thông quavalidateHashed().- Các đối tượng cấu hình là
final readonly— một bí mật đã đặt thì không thể thay đổi. - Hãy lấy bí mật từ biến môi trường hoặc trình quản lý bí mật. Đừng bao giờ commit chúng. Gói tuân theo nền tảng bảo mật rộng hơn của NextPDF: PHPStan Level 10,
declare(strict_types=1)trên mọi tệp, không dùngeval()/exec(), GitHub Actions được ghim theo SHA.
Những điều gói này không khẳng định
Phần tiêu đề “Những điều gói này không khẳng định”- Gói không nêu bất kỳ giới hạn nền tảng Cloudflare nào (thời gian CPU của Worker, bộ nhớ, mức trần kích thước body của yêu cầu, hay số lượng subrequest). Những giới hạn về kích thước và thời gian duy nhất mà tài liệu này nêu là những giới hạn do chính gói thực thi, được liệt kê ở trên và trong /integrations/cloudflare/configuration/. Về các giới hạn nền tảng, hãy tham khảo tài liệu chính thức của Cloudflare và bản triển khai Worker của riêng bạn.
- Gói không ký PDF và không đưa ra bất kỳ tuyên bố nào về tuân thủ chữ ký. Khi cần chữ ký, hãy kết xuất tại đây, rồi ký bằng engine. NextPDF Pro chỉ cung cấp ký PDF Advanced Electronic Signatures (PAdES) B-B; các hồ sơ xác thực dài hạn là một năng lực của bản Enterprise và nằm ngoài phạm vi của cầu nối này.
- Gói không chứng nhận, bảo đảm, hay làm cho pipeline trở nên “chống giả mạo”. Gói chỉ triển khai những biện pháp kiểm soát cụ thể, có thể kiểm chứng qua mã nguồn, được mô tả trên trang này.
Sổ tay vận hành
Phần tiêu đề “Sổ tay vận hành”| Triệu chứng | Kiểm tra trước tiên |
|---|---|
Worker URL must use HTTPS | Hãy kiểm tra scheme của workerUrl đã cấu hình. |
private or reserved IP | Các bản ghi DNS của hostname Worker; hãy tìm bản ghi phân giải vào không gian RFC 1918 / loopback / RFC 3927. |
DNS answer changed since validation | DNS không ổn định hoặc một nỗ lực rebinding; hãy phân giải lại và kiểm tra toàn bộ tập bản ghi. |
cURL transport error | Đường truyền mạng, chuỗi TLS, và — nếu có đặt ghim — kiểm tra xem SPKI của chứng chỉ được phục vụ có còn nằm trong tập ghim hay không. |
| Các lần kết xuất thất bại ngay sau một lần xoay vòng chứng chỉ | Tập ghim không có ghim dự phòng phù hợp. Hãy thêm SPKI mới làm ghim dự phòng trước khi xoay vòng. |
is not installed / no LocalRendererFactoryInterface | Cơ chế dự phòng được bật nhưng không có factory nào được nối, hoặc nextpdf/artisan vắng mặt. |
| Việc từ chối do giới hạn tốc độ không nhất quán giữa các node | Bộ giới hạn trong bộ nhớ hoạt động theo từng tiến trình; hãy đặt một kho lưu trữ dùng chung ở phía trước nó. |
Báo cáo sự cố
Phần tiêu đề “Báo cáo sự cố”Hãy báo cáo lỗ hổng qua GitHub Security Advisories hoặc đầu mối liên hệ bảo mật trong SECURITY.md của repository. Đừng gửi vấn đề bảo mật dưới dạng GitHub issue công khai.
Xem thêm
Phần tiêu đề “Xem thêm”- /integrations/cloudflare/overview/ — vì sao gói này được định hình quanh ranh giới.
- /integrations/cloudflare/configuration/ — các trường tập ghim và giới hạn.
- /integrations/cloudflare/troubleshooting/ — ánh xạ đầy đủ từ lỗi sang ngoại lệ.