Bỏ qua để đến nội dung

Bảo mật và vận hành Artisan

Cầu nối kết xuất HTML xử lý nội dung có thể không đáng tin cậy bên trong Chrome, phía sau hai rào cản mạng độc lập và một chính sách nội dung nghiêm ngặt. Sandbox cấp hệ điều hành của Chrome là một biện pháp kiểm soát riêng biệt, tùy chọn, với những giới hạn rõ ràng. Trang này ghi lại ranh giới đó và không tuyên bố rằng ranh giới này là tuyệt đối.

Một lần kết xuất là một lần thực thi yêu cầu phía máy chủ: ứng dụng của bạn giao HTML cho một công cụ trình duyệt vốn có thể nạp tài nguyên theo mặc định. Khi đầu vào không đáng tin cậy điều khiển một lần nạp ra bên ngoài, rủi ro phát sinh là giả mạo yêu cầu phía máy chủ (SSRF): mục Common Weakness Enumeration (CWE) CWE-918 định nghĩa đó là việc máy chủ lấy nội dung của một URL được cung cấp mà không bảo đảm đầy đủ rằng yêu cầu đi đến đúng đích mong đợi. SSRF (CWE-918) là một điểm yếu thuộc CWE Top 25. Open Worldwide Application Security Project (OWASP) Application Security Verification Standard (ASVS) yêu cầu bạn kiểm soát các yêu cầu ra bên ngoài từ những thành phần máy chủ thay vì để chúng diễn ra ngầm định. OWASP SSRF Prevention Cheat Sheet coi việc chặn ở tầng mạng đối với các lệnh gọi đến những đích tùy ý là biện pháp kiểm soát mạnh. Cách tiếp cận mạng từ chối theo mặc định dưới đây là cách cầu nối đáp ứng yêu cầu đó. National Institute of Standards and Technology (NIST) Special Publication (SP) 800-53 SC-7 mô tả cùng một nguyên tắc ranh giới: từ chối tất cả và chỉ cho phép theo ngoại lệ, được cầu nối áp dụng ở tầng truyền tải.

Lưu trú dữ liệu và các biện pháp giảm thiểu PII

Phần tiêu đề “Lưu trú dữ liệu và các biện pháp giảm thiểu PII”

HTML được truyền vào cầu nối được xử lý hoàn toàn trong tiến trình và trong phiên bản Chrome cục bộ. Cầu nối không tự thực hiện bất kỳ lệnh gọi mạng ra ngoài nào và chặn Chrome thực hiện mọi lệnh gọi như vậy (xem mô hình mạng bên dưới), nên nội dung đầu vào không rời khỏi máy chủ thông qua bộ kết xuất. Thông tin định danh cá nhân (PII) trong đầu vào được kết xuất vào đầu ra Portable Document Format (PDF) mà bạn tạo ra, vì vậy hãy áp dụng cho đầu ra các biện pháp kiểm soát lưu trú giống như với đầu vào. Cầu nối không lưu giữ đầu vào hay đầu ra xuống đĩa; việc lưu giữ là trách nhiệm của bên gọi.

Đo lường từ xa an toàn và làm sạch nhật ký

Phần tiêu đề “Đo lường từ xa an toàn và làm sạch nhật ký”

ChromeHtmlRendererBrowserPool nhận một LoggerInterface tùy chọn theo PHP Standard Recommendation (PSR)-3. Cầu nối chỉ ghi nhật ký siêu dữ liệu vận hành: độ dài byte đầu vào, chiều rộng và chiều cao đích, độ dài byte đầu ra, chiều cao nội dung đo được, sự kiện khởi chạy trình duyệt với đường dẫn nhị phân đã cấu hình, thông báo khởi động lại kèm số lần kết xuất, và các sự kiện đóng. Cầu nối không ghi nhật ký nội dung HTML, byte đã kết xuất hay văn bản trích xuất. Điều này phù hợp với hướng dẫn của NIST SP 800-92 về việc ghi nhật ký các sự kiện vận hành trong khi giữ dữ liệu nhạy cảm bên ngoài nhật ký. Đường dẫn nhị phân được ghi nhật ký. Hãy coi đó là siêu dữ liệu triển khai không nhạy cảm. Các bài kiểm thử xác nhận hình dạng của các lệnh gọi ghi nhật ký trong tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSizetests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath.

Mô hình cô lập mạng (phòng thủ theo lớp)

Phần tiêu đề “Mô hình cô lập mạng (phòng thủ theo lớp)”

Cầu nối áp dụng hai rào cản độc lập để việc vượt qua một lớp không làm lộ máy chủ:

  1. Content-Security-Policy. Mỗi lần kết xuất được bọc bởi ChromeSecurityPolicy::wrapHtml() trong một tài liệu mang theo:

    default-src 'none'; style-src 'unsafe-inline'; img-src data:;
    base-uri 'none'; form-action 'none'; frame-ancestors 'none';
    navigate-to 'none';

    Chỉ thị Content Security Policy (CSP) default-src 'none' từ chối tất cả các nguồn gốc tài nguyên. img-src data: chỉ cho phép ảnh nội tuyến. navigate-to 'none' chặn điều hướng phía máy khách. style-src 'unsafe-inline' là nới lỏng duy nhất cần thiết để Chrome printToPDF áp dụng các kiểu nội tuyến. Điều này được xác minh trong src/Artisan/ChromeSecurityPolicy.php và được khẳng định bởi ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives.

  2. Chặn truyền tải Chrome DevTools Protocol (CDP). Trước khi nội dung được nạp, ChromeHtmlRenderer phát ra Network.enable rồi sau đó Network.setBlockedURLs với mẫu ['*']. Điều này chặn mọi URL tài nguyên con tại tầng truyền tải Chrome DevTools Protocol, độc lập với CSP. Điều này được xác minh trong src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests() và được khẳng định bởi ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests (bài kiểm thử kiểm tra đúng thứ tự phương thức CDP và tham số ['urls' => ['*']]). Đây là biện pháp chặn ở tầng mạng mà hướng dẫn SSRF của OWASP khuyến nghị là biện pháp kiểm soát mạnh nhất, và cũng là một dạng từ chối tất cả ở cấp truyền tải, nhất quán với NIST SP 800-53 SC-7.

Kết quả: một URL <img> từ xa, bảng định kiểu, phông chữ, tập lệnh hay iframe trong đầu vào sẽ không được nạp. Cầu nối không hiện thực danh sách miền cho phép hay bộ lọc IP riêng tư vì không cần đến chúng: nó hoàn toàn không cho phép bất kỳ lệnh nạp tài nguyên con ra bên ngoài nào.

Lưu ý về sai lệch: docblock của nextpdf/core trên writeHtmlChrome() nói rằng Chrome “sẽ nạp tài nguyên bên ngoài” và khuyên cấu hình một chính sách để “chặn các dải IP riêng tư và giới hạn các miền được phép.” Điều đó mô tả một mô hình danh sách cho phép có thể cấu hình. ChromeSecurityPolicy được phát hành kèm Artisan không cung cấp danh sách cho phép; nó chặn tất cả các yêu cầu tài nguyên con một cách vô điều kiện. Mã nguồn, chứ không phải docblock của core, mới là nguồn có thẩm quyền. Sai lệch này được ghi nhận để chuyển cho nhóm tài liệu của core.

Kiểm định đầu vào (trước khi vào Chrome)

Phần tiêu đề “Kiểm định đầu vào (trước khi vào Chrome)”

ChromeSecurityPolicy::validate() chạy trước khi cầu nối liên hệ với Chrome và từ chối:

Kiểm traGiới hạnLý do
Kích thước HTML> maxHtmlSize (mặc định 5 MB)Giới hạn cạn kiệt tài nguyên (tiêu thụ tài nguyên không kiểm soát, thuộc CWE Top 25)
URI dữ liệu base64nhóm bắt >= 13_000_000 byteGiới hạn chống bom giải nén
<meta http-equiv="refresh">bất kỳ (không phân biệt hoa thường, single/double)Chặn các chuyển hướng phía máy khách đến các điểm cuối nội bộ, một vector điều hướng SSRF

Việc chặn meta-refresh là một biện pháp gia cố SSRF tường minh. Nếu không có biện pháp này, HTML do kẻ tấn công kiểm soát có thể chuyển hướng Chrome đến một điểm cuối siêu dữ liệu đám mây trước khi printToPDF. Hành vi tại ranh giới được khẳng định xuyên suốt ChromeSecurityPolicyTest (validateThrowsOnOversizedHtml, validateRejectsMetaRefreshRedirect, validateRejectsMetaRefreshCaseInsensitive, validateRejectsMetaRefreshWithSingleQuotes, validateRejectsOversizedBase64DataUri, validateRejectsBase64DataUriAtExactThreshold).

Ngoài ra, ChromeSecurityPolicy::wrapHtml() loại bỏ </style> khỏi defaultCss trước khi tiêm vào, nhằm ngăn việc thoát khỏi khối style để vào ngữ cảnh tập lệnh (được khẳng định bởi ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss).

Ranh giới sandbox của Chrome — được nêu tường minh

Phần tiêu đề “Ranh giới sandbox của Chrome — được nêu tường minh”

Sandbox cấp hệ điều hành của Chrome là một biện pháp kiểm soát riêng biệt với các rào cản mạng ở trên, và cầu nối không bảo đảm biện pháp đó.

  • Theo mặc định, noSandboxfalse, nên Chrome khởi chạy với sandbox riêng được bật. Cầu nối không hiện thực sandbox đó; nó dựa vào sandbox của tệp nhị phân Chrome, vốn phụ thuộc vào sự hỗ trợ của nhân hệ điều hành máy chủ.
  • Đặt noSandbox: true sẽ khởi chạy Chrome với --no-sandbox. Điều này loại bỏ sandbox cô lập tiến trình của Chrome. Tùy chọn này được cung cấp cho các container nơi sandbox không thể khởi tạo. Đó là một sự suy giảm thực sự về mức độ cô lập: khi bộ kết xuất bị xâm phạm, sandbox của Chrome sẽ không còn ngăn chặn được nữa.
  • Các rào cản mạng của cầu nối (CSP + chặn CDP) vẫn có hiệu lực dù sandbox có được bật hay không, nhưng chúng không thay thế cho cô lập tiến trình. Hướng dẫn về đặc quyền tối thiểu của OWASP ASVS được áp dụng: chạy Chrome với tư cách người dùng không phải root, trong một container bị ràng buộc, chỉ dùng noSandbox ở nơi không thể tránh khỏi, và coi một triển khai --no-sandbox như một yêu cầu mức tin cậy cao hơn đối với đầu vào.

Tài liệu này không tuyên bố rằng cầu nối “an toàn theo mặc định” hay “chống giả mạo”. Tài liệu cũng không tuyên bố rằng việc tắt sandbox là an toàn. Nó nêu ra các biện pháp kiểm soát đang tồn tại và điểm dừng của chúng. Việc cấp phát một container có khả năng dùng sandbox được trình bày trên trang /integrations/artisan/chrome-renderer-setup/.

Các chế độ lỗi này được liệt kê từ src/Artisan/Exception/ và mã render/transport:

Điều kiệnBiểu lộ dưới dạngNguồn
chrome-php/chrome không cóChromeNotAvailableException (kèm lệnh cài đặt)BrowserPool::getBrowser()
HTML vượt quá maxHtmlSizeRuntimeException (“exceeds maximum allowed size”)ChromeSecurityPolicy::validate()
URI dữ liệu base64 quá cỡRuntimeException (“oversized base64 data URI”)ChromeSecurityPolicy::validate()
Meta-refresh bị cấmRuntimeException (“forbidden meta refresh redirect”)ChromeSecurityPolicy::validate()
Chrome khởi chạy / hết thời gian chờ / sậpChromeRenderException (bọc nguyên nhân gốc)ChromeHtmlRenderer::render()
Chrome trả về PDF rỗngChromeRenderException (“returned empty data”)ChromeHtmlRenderer::render()
Trang không có luồng nội dungPdfParseExceptionPageImporter::import()

Nếu ChromeRenderException được phát ra trong quá trình kết xuất, nó được ném lại nguyên trạng. Bất kỳ Throwable nào khác đều được bọc thành ChromeRenderException, đồng thời giữ lại ngoại lệ trước đó (được khẳng định bởi ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping::renderWrapsUnexpectedThrowablesWithChromeRenderException). Trang Chrome luôn được đóng trong một khối finally, kể cả khi gặp lỗi.

  • Kích thước đầu vào: maxHtmlSize (mặc định 5 MB) và giới hạn 13 MB cho URI dữ liệu base64.
  • Thời gian: renderTimeout giây giới hạn cả việc nạp nội dung lẫn các lệnh gọi đồng bộ CDP. Các lệnh điều khiển CDP dùng một thời gian chờ cố định là 5 giây.
  • Tiến trình: BrowserPool khởi động lại Chrome sau mỗi 100 lần kết xuất để giới hạn mức tăng bộ nhớ và đóng tiến trình khi gọi close() / khi bị hủy.

Đây là các ngưỡng giới hạn, không phải hạn ngạch. Với bất kỳ đường dẫn nào nhận đầu vào không đáng tin cậy, hãy vẫn dùng một giới hạn tài nguyên ở cấp máy chủ (cgroup, ulimit, ngân sách yêu cầu), nhất quán với hướng dẫn về tiêu thụ tài nguyên của CWE Top 25.

Tiêm một logger PSR-3 để ghi lại lúc bắt đầu kết xuất (kích thước, chiều rộng, chiều cao), lúc hoàn tất kết xuất (kích thước đầu ra, chiều cao nội dung), lúc khởi chạy trình duyệt (đường dẫn nhị phân), lúc khởi động lại trình duyệt (số lần kết xuất), và lúc đóng trình duyệt (số lần kết xuất). Đây là những sự kiện duy nhất được phát ra, và chúng không mang theo nội dung tải. Hãy dùng chúng cho các mục tiêu mức dịch vụ (SLO) về độ trễ và cảnh báo về tỷ lệ khởi động lại.

Tuyên bốTham chiếuclause_idreference_id
Các yêu cầu ra bên ngoài từ những thành phần máy chủ phải được kiểm soátOWASP ASVS 5.0§ (SSRF/kiểm soát yêu cầu ra ngoài)
SSRF = máy chủ lấy một URL được cung cấp mà không kiểm định đích đếnCWE Top 25 2025 (CWE-918)cwe_top25_2025#x28.x2.p2
SSRF (CWE-918) là một điểm yếu thuộc CWE Top 25CWE Top 25 2025cwe_top25_2025#x1.p73
Tiêu thụ tài nguyên không kiểm soát là một điểm yếu thuộc CWE Top 25CWE Top 25 2025 (CWE-400)cwe_top25_2025#x19.x2.p2
Bảo vệ ranh giới từ chối theo mặc định (cho phép theo ngoại lệ)NIST SP 800-53 Rev 5 SC-7SC-7
Chặn ở tầng mạng các lệnh gọi đến những đích tùy ý là biện pháp kiểm soát SSRF mạnhOWASP Cheat Sheet Series (SSRF Prevention §Network layer)owasp_cheatsheet_series#x132.x2
Bảo vệ các thành phần nạp URL trước SSRFOWASP Cheat Sheet Series§ (phòng chống SSRF, công cụ nạp URL)
Cô lập việc kết xuất nội dung không đáng tin cậy, đặc quyền tối thiểuOWASP ASVS 5.0§ (sandbox / đặc quyền tối thiểu)
Ghi nhật ký các sự kiện vận hành; giữ nội dung tải ra khỏi nhật kýNIST SP 800-92§ (hướng dẫn về nội dung nhật ký)

Các trích dẫn được lấy thông qua công cụ tuân thủ của NextPDF (bản kê khai kho ngữ liệu 1d05b7c4…d790b6); văn bản điều khoản được diễn giải lại, không bao giờ trích dẫn nguyên văn.

Mối đe dọaBiện pháp kiểm soátRủi ro còn lại
SSRF qua tài nguyên con từ xaCSP default-src 'none' + CDP setBlockedURLs('*')Một lỗi của công cụ Chrome có thể vượt qua cả hai rào cản (phòng thủ theo lớp làm giảm rủi ro, nhưng không loại bỏ nó)
SSRF qua điều hướng meta-refreshViệc kiểm định trước khi vào Chrome từ chối thẻ nàyMột vector điều hướng mới không khớp với mẫu
Cạn kiệt tài nguyênGiới hạn kích thước đầu vào + giới hạn base64 + thời gian chờ + khởi động lại sau 100 lần kết xuấtKhông có hạn ngạch theo từng máy chủ; hãy kết hợp với cgroup/ulimit
Tiến trình bộ kết xuất bị xâm phạmSandbox của Chrome khi được bậtnoSandbox: true loại bỏ hoàn toàn biện pháp kiểm soát này
Thoát khỏi khối style / tiêm mã</style> được loại bỏ trong defaultCss; CSP chặn tập lệnhTiêm mã thông qua một vector trong tương lai không bị loại bỏ

Cầu nối không thực hiện thao tác mật mã nào. Nó tạo ra các byte PDF thông qua Chrome và nhúng chúng vào. Việc ký, mã hóa và hành vi ở chế độ Federal Information Processing Standards (FIPS) là những vấn đề thuộc core/Premium và không bị Artisan tác động.

  • /integrations/artisan/configuration/
  • /integrations/artisan/chrome-renderer-setup/
  • /integrations/artisan/troubleshooting/
  • /integrations/artisan/production-usage/
  • /integrations/artisan/overview/