ข้ามไปยังเนื้อหา

ความปลอดภัยและการดำเนินงานของ Artisan

Bridge เรนเดอร์ HTML ที่อาจไม่น่าเชื่อถือภายใน Chrome ภายใต้กำแพงเครือข่ายอิสระสองชั้นและนโยบายเนื้อหาที่เข้มงวด แซนด์บ็อกซ์ระดับระบบปฏิบัติการของ Chrome เป็นการควบคุมแยกต่างหากและเป็นทางเลือก โดยมีข้อจำกัดที่ชัดเจน หน้านี้บันทึกขอบเขตเหล่านั้นไว้ และไม่ได้อ้างว่าขอบเขตดังกล่าวเป็นสิ่งสัมบูรณ์

การเรนเดอร์ถือเป็นการดำเนินการคำขอฝั่งเซิร์ฟเวอร์ แอปพลิเคชันของคุณส่ง HTML ไปยังเอนจินเบราว์เซอร์ที่ดึงทรัพยากรได้โดยค่าเริ่มต้น เมื่ออินพุตที่ไม่น่าเชื่อถือกำหนดการดึงข้อมูลขาออก ความเสี่ยงคือ server-side request forgery (SSRF) รายการ Common Weakness Enumeration (CWE) หมายเลข CWE-918 นิยามกรณีที่เซิร์ฟเวอร์ดึงเนื้อหาของ URL ที่ได้รับมา โดยไม่มีหลักประกันเพียงพอว่าคำขอจะไปถึงปลายทางที่คาดหวัง SSRF (CWE-918) เป็นจุดอ่อนใน CWE Top 25 Open Worldwide Application Security Project (OWASP) Application Security Verification Standard (ASVS) กำหนดให้คุณควบคุมคำขอขาออกจากองค์ประกอบฝั่งเซิร์ฟเวอร์ แทนที่จะปล่อยให้เกิดขึ้นโดยปริยาย OWASP SSRF Prevention Cheat Sheet ถือว่าการปฏิเสธการเรียกไปยังปลายทางใด ๆ ที่ระดับเครือข่ายเป็นการควบคุมที่แข็งแกร่ง แนวทางเครือข่ายแบบปฏิเสธโดยค่าเริ่มต้นด้านล่างนี้คือการตอบสนองของ bridge ต่อข้อกำหนดดังกล่าว National Institute of Standards and Technology (NIST) Special Publication (SP) 800-53 SC-7 อธิบายหลักการป้องกันขอบเขตที่ปฏิเสธทั้งหมดและอนุญาตเฉพาะข้อยกเว้น ซึ่งเป็นหลักการเดียวกับที่ bridge นำมาใช้ที่ชั้นการขนส่ง

ถิ่นที่อยู่ของข้อมูลและมาตรการลดความเสี่ยง PII

หัวข้อที่มีชื่อว่า “ถิ่นที่อยู่ของข้อมูลและมาตรการลดความเสี่ยง PII”

HTML ที่ส่งไปยัง bridge จะถูกประมวลผลทั้งหมดภายในกระบวนการและอินสแตนซ์ Chrome ในเครื่อง Bridge ไม่ได้เรียกเครือข่ายขาออกใด ๆ เอง และบล็อก Chrome ไม่ให้เรียกเครือข่ายใด ๆ (ดูโมเดลเครือข่ายด้านล่าง) ดังนั้นเนื้อหาอินพุตจึงไม่ออกจากโฮสต์ผ่านตัวเรนเดอร์ ข้อมูลส่วนบุคคลที่ระบุตัวตนได้ (PII) ในอินพุตจะถูกเรนเดอร์ลงในเอาต์พุต Portable Document Format (PDF) ที่คุณสร้างขึ้น ดังนั้นจึงควรควบคุมถิ่นที่อยู่ของข้อมูลสำหรับเอาต์พุตเช่นเดียวกับอินพุต Bridge ไม่ได้คงข้อมูลอินพุตหรือเอาต์พุตไว้บนดิสก์ การเก็บคงไว้เป็นความรับผิดชอบของผู้เรียกใช้

เทเลเมทรีที่ปลอดภัยและการล้างข้อมูลออกจากบันทึก

หัวข้อที่มีชื่อว่า “เทเลเมทรีที่ปลอดภัยและการล้างข้อมูลออกจากบันทึก”

ChromeHtmlRenderer และ BrowserPool รองรับ PHP Standard Recommendation (PSR)-3 LoggerInterface แบบไม่บังคับ Bridge บันทึกเฉพาะข้อมูลเมตาด้านการดำเนินงานเท่านั้น ได้แก่ ความยาวไบต์ของอินพุต ความกว้างและความสูงเป้าหมาย ความยาวไบต์ของเอาต์พุต ความสูงของเนื้อหาที่วัดได้ เหตุการณ์เปิดเบราว์เซอร์ด้วยเส้นทางไบนารีที่กำหนดค่าไว้ การแจ้งรีสตาร์ตพร้อมจำนวนการเรนเดอร์ และเหตุการณ์ปิด Bridge ไม่ บันทึกเนื้อหา HTML, ไบต์ที่เรนเดอร์ หรือข้อความที่สกัดออกมา แนวทางนี้สอดคล้องกับ NIST SP 800-92 ที่ให้บันทึกเหตุการณ์ด้านการดำเนินงาน และเก็บเพย์โหลดที่ละเอียดอ่อนไว้นอกบันทึก เส้นทางไบนารีถูกบันทึกไว้ ควรถือเป็นข้อมูลเมตาการปรับใช้ที่ไม่ละเอียดอ่อน การทดสอบยืนยันรูปแบบการเรียกบันทึกใน tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize และ tests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath

โมเดลการแยกตัวของเครือข่าย (การป้องกันเชิงลึก)

หัวข้อที่มีชื่อว่า “โมเดลการแยกตัวของเครือข่าย (การป้องกันเชิงลึก)”

Bridge ใช้กำแพงอิสระสองชั้น เพื่อให้การข้ามผ่านชั้นหนึ่งไม่ทำให้โฮสต์ถูกเปิดเผย:

  1. Content-Security-Policy การเรนเดอร์ทุกครั้งจะถูก ChromeSecurityPolicy::wrapHtml() ห่อหุ้มไว้ในเอกสารที่มี:

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

    คำสั่ง Content Security Policy (CSP) default-src 'none' ปฏิเสธแหล่งที่มาของทรัพยากรทั้งหมด img-src data: อนุญาตเฉพาะรูปภาพแบบอินไลน์เท่านั้น navigate-to 'none' บล็อกการนำทางฝั่งไคลเอนต์ style-src 'unsafe-inline' เป็นการผ่อนปรนเพียงอย่างเดียวที่จำเป็นเพื่อให้ Chrome printToPDF ใช้สไตล์แบบอินไลน์ได้ ตรวจสอบได้ใน src/Artisan/ChromeSecurityPolicy.php และยืนยันโดย ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives

  2. การบล็อกการขนส่ง Chrome DevTools Protocol (CDP) ก่อนโหลดเนื้อหา ChromeHtmlRenderer จะส่งคำสั่ง Network.enable แล้วตามด้วย Network.setBlockedURLs ด้วยรูปแบบ ['*'] การตั้งค่านี้บล็อก URL ของทรัพยากรย่อยทั้งหมดที่ชั้นการขนส่งของ Chrome DevTools Protocol โดยไม่ขึ้นกับ CSP ตรวจสอบได้ใน src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests() และยืนยันโดย ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests (ซึ่งตรวจสอบลำดับเมธอด CDP ที่แน่นอนและพารามิเตอร์ ['urls' => ['*']]) นี่คือการบล็อกระดับเครือข่ายที่แนวทาง OWASP SSRF แนะนำว่าเป็นการควบคุมที่แข็งแกร่งที่สุด และเป็นการปฏิเสธทั้งหมดในระดับการขนส่งที่สอดคล้องกับ NIST SP 800-53 SC-7

ผลลัพธ์คือ <img> สไตล์ชีต ฟอนต์ สคริปต์ หรือ URL ของ iframe ระยะไกลในอินพุตจะไม่ถูกโหลด Bridge ไม่ได้ใช้รายการอนุญาตโดเมนหรือตัวกรอง IP ส่วนตัว เพราะไม่จำเป็น เนื่องจาก bridge ไม่อนุญาตให้ดึงทรัพยากรย่อยขาออกใด ๆ เลย

หมายเหตุความเบี่ยงเบน: docblock ของ nextpdf/core บน writeHtmlChrome() ระบุว่า Chrome “will fetch external resources” และแนะนำให้กำหนดค่านโยบายเพื่อ “block private IP ranges and limit allowed domains” ข้อความนั้นอธิบายโมเดลรายการอนุญาตที่กำหนดค่าได้ ChromeSecurityPolicy ของ Artisan ที่จัดส่งมาไม่ได้เปิดเผยรายการอนุญาต แต่จะบล็อกคำขอทรัพยากรย่อย ทั้งหมด อย่างไม่มีเงื่อนไข โค้ดถือเป็นแหล่งอ้างอิงหลัก ไม่ใช่ docblock ของ core ความเบี่ยงเบนนี้บันทึกไว้ให้ทีมเอกสารของ core

ChromeSecurityPolicy::validate() ทำงานก่อนที่ bridge จะติดต่อ Chrome และปฏิเสธ:

การตรวจสอบขีดจำกัดเหตุผล
ขนาด HTML> maxHtmlSize (ค่าเริ่มต้น 5 MB)ขอบเขตสำหรับการใช้ทรัพยากรจนหมด (การใช้ทรัพยากรอย่างไม่มีการควบคุมใน CWE Top 25)
Base64 data URI (URI ข้อมูลแบบ Base64)ค่าที่จับได้ >= 13_000_000 ไบต์ขอบเขตการป้องกัน decompression bomb
<meta http-equiv="refresh">ใด ๆ (ไม่คำนึงถึงตัวพิมพ์ใหญ่เล็ก หรือใช้ single/double quote)บล็อกการเปลี่ยนเส้นทางฝั่งไคลเอนต์ไปยังปลายทางภายใน ซึ่งเป็นเวกเตอร์การนำทางแบบ SSRF

การบล็อก meta-refresh เป็นการเพิ่มความแข็งแกร่งต่อ SSRF โดยตรง หากไม่มีการบล็อกนี้ HTML ที่ผู้โจมตีควบคุมอาจเปลี่ยนเส้นทาง Chrome ไปยังปลายทางเมตาดาทาบนคลาวด์ก่อน printToPDF ยืนยันพฤติกรรมตามขอบเขตนี้ไว้ทั่วทั้ง ChromeSecurityPolicyTest (validateThrowsOnOversizedHtml, validateRejectsMetaRefreshRedirect, validateRejectsMetaRefreshCaseInsensitive, validateRejectsMetaRefreshWithSingleQuotes, validateRejectsOversizedBase64DataUri, validateRejectsBase64DataUriAtExactThreshold)

นอกจากนี้ ChromeSecurityPolicy::wrapHtml() จะลบ </style> ออกจาก defaultCss ก่อนการแทรก เพื่อป้องกันการหลุดออกจากบล็อกสไตล์ไปยังบริบทสคริปต์ (ยืนยันโดย ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss)

ขอบเขตของแซนด์บ็อกซ์ Chrome — ระบุไว้อย่างชัดเจน

หัวข้อที่มีชื่อว่า “ขอบเขตของแซนด์บ็อกซ์ Chrome — ระบุไว้อย่างชัดเจน”

แซนด์บ็อกซ์ระดับระบบปฏิบัติการของ Chrome เป็นการควบคุมที่ แยกต่างหาก จากกำแพงเครือข่ายข้างต้น และ bridge ไม่รับประกันว่าการควบคุมนี้จะมีอยู่

  • โดยค่าเริ่มต้น noSandbox มีค่าเป็น false ดังนั้น Chrome จึงเริ่มทำงานพร้อมแซนด์บ็อกซ์ของตนเอง Bridge ไม่ได้จัดเตรียมแซนด์บ็อกซ์นี้เอง แต่อาศัยแซนด์บ็อกซ์ของไบนารี Chrome ซึ่งขึ้นอยู่กับการสนับสนุนเคอร์เนลของโฮสต์
  • การตั้งค่า noSandbox: true จะเปิด Chrome ด้วย --no-sandbox ซึ่ง นำออก แซนด์บ็อกซ์การแยกกระบวนการของ Chrome ตัวเลือกนี้มีไว้สำหรับคอนเทนเนอร์ที่ไม่สามารถเริ่มแซนด์บ็อกซ์ได้ และลดการแยกตัวลงอย่างแท้จริง: หากตัวเรนเดอร์ถูกบุกรุก จะไม่ถูกจำกัดด้วยแซนด์บ็อกซ์ของ Chrome อีกต่อไป
  • กำแพงเครือข่ายของ bridge (CSP + การบล็อก CDP) ยังคงมีผลบังคับไม่ว่าจะเปิดใช้งานแซนด์บ็อกซ์หรือไม่ก็ตาม แต่กำแพงเหล่านี้ไม่ใช่สิ่งทดแทนการแยกกระบวนการ แนวทางสิทธิ์น้อยที่สุดของ OWASP ASVS ยังคงต้องใช้: ให้รัน Chrome ในฐานะผู้ใช้ที่ไม่ใช่ root ในคอนเทนเนอร์ที่มีข้อจำกัด โดยใช้ noSandbox เฉพาะเมื่อหลีกเลี่ยงไม่ได้เท่านั้น และถือว่าการปรับใช้ --no-sandbox เป็นข้อกำหนดที่ต้องเชื่อถืออินพุตมากขึ้น

เอกสารนี้ไม่ได้อ้างว่า bridge “secure by default” หรือ “tamper-proof” และไม่ได้อ้างว่าการปิดแซนด์บ็อกซ์เป็นสิ่งที่ปลอดภัย เอกสารนี้ระบุการควบคุมที่มีอยู่และจุดที่การควบคุมเหล่านั้นสิ้นสุดลง การจัดเตรียมคอนเทนเนอร์ที่รองรับแซนด์บ็อกซ์อธิบายไว้ในหน้า /integrations/artisan/chrome-renderer-setup/

โหมดความล้มเหลวเหล่านี้แจกแจงจาก src/Artisan/Exception/ และโค้ด render/transport:

เงื่อนไขปรากฏเป็นแหล่งที่มา
ไม่มีไลบรารี chrome-php/chromeChromeNotAvailableException (พร้อมคำสั่งติดตั้ง)BrowserPool::getBrowser()
HTML เกิน maxHtmlSizeRuntimeException (“exceeds maximum allowed size” — เกินขนาดสูงสุดที่อนุญาต)ChromeSecurityPolicy::validate()
Base64 data URI ที่มีขนาดใหญ่เกินไปRuntimeException (“oversized base64 data URI” — Base64 data URI มีขนาดใหญ่เกินไป)ChromeSecurityPolicy::validate()
meta-refresh ที่ต้องห้ามRuntimeException (“forbidden meta refresh redirect” — การเปลี่ยนเส้นทาง meta refresh ที่ต้องห้าม)ChromeSecurityPolicy::validate()
Chrome เปิดไม่สำเร็จ / หมดเวลา / ขัดข้องChromeRenderException (ห่อหุ้มสาเหตุ)ChromeHtmlRenderer::render()
Chrome คืนค่า PDF ที่ว่างเปล่าChromeRenderException (“returned empty data” — คืนค่าข้อมูลว่างเปล่า)ChromeHtmlRenderer::render()
หน้าไม่มีสตรีมเนื้อหาPdfParseExceptionPageImporter::import()

หากมีการยก ChromeRenderException ระหว่างการเรนเดอร์ จะโยนซ้ำออกไปโดยไม่เปลี่ยนแปลง Throwable อื่นทั้งหมดจะถูกห่อหุ้มเป็น ChromeRenderException โดยรักษาข้อยกเว้นก่อนหน้าไว้ (ยืนยันโดย ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping และ ::renderWrapsUnexpectedThrowablesWithChromeRenderException) หน้าใน Chrome จะถูกปิดเสมอในบล็อก finally แม้ในกรณีที่ล้มเหลว

  • ขนาดอินพุต: maxHtmlSize (ค่าเริ่มต้น 5 MB) และเพดาน base64 data-URI ที่ 13 MB
  • เวลา: renderTimeout วินาทีกำหนดขอบเขตทั้งการโหลดเนื้อหาและการเรียก CDP แบบ sync ส่วนคำสั่งควบคุม CDP ใช้การหมดเวลาคงที่ 5 วินาที
  • กระบวนการ: BrowserPool รีสตาร์ต Chrome ทุก ๆ 100 การเรนเดอร์เพื่อจำกัดการเติบโตของหน่วยความจำ และปิดกระบวนการเมื่อมีการเรียก close() / การทำลายอ็อบเจ็กต์

สิ่งเหล่านี้เป็นขอบเขต ไม่ใช่โควตา สำหรับเส้นทางใด ๆ ที่เปิดรับอินพุตที่ไม่น่าเชื่อถือ ยังคงต้องใช้ขีดจำกัดทรัพยากรระดับโฮสต์ (cgroup, ulimit, request budget) ให้สอดคล้องกับแนวทางด้านการใช้ทรัพยากรใน CWE Top 25

กำหนด PSR-3 logger เพื่อบันทึกการเริ่มเรนเดอร์ (ขนาด ความกว้าง ความสูง) การเรนเดอร์เสร็จสมบูรณ์ (ขนาดเอาต์พุต ความสูงของเนื้อหา) การเปิดเบราว์เซอร์ (เส้นทางไบนารี) การรีสตาร์ตเบราว์เซอร์ (จำนวนการเรนเดอร์) และการปิดเบราว์เซอร์ (จำนวนการเรนเดอร์) เหตุการณ์เหล่านี้เป็นเหตุการณ์เดียวที่ถูกปล่อยออกมา และไม่มีเนื้อหาเพย์โหลดแนบมาด้วย ใช้เหตุการณ์เหล่านี้สำหรับ service-level objectives (SLOs) ด้านความหน่วงและการแจ้งเตือนอัตราการรีสตาร์ต

คำกล่าวอ้างข้อมูลอ้างอิงclause_id (รหัสข้อกำหนด)reference_id (รหัสอ้างอิง)
ต้องควบคุมคำขอขาออกจากองค์ประกอบฝั่งเซิร์ฟเวอร์OWASP ASVS 5.0§ (SSRF/outbound control — การควบคุมคำขอขาออก)
SSRF = เซิร์ฟเวอร์ดึง URL ที่ได้รับมาโดยไม่ตรวจสอบปลายทางCWE Top 25 2025 (CWE-918) — รายการจุดอ่อนcwe_top25_2025#x28.x2.p2
SSRF (CWE-918) เป็นจุดอ่อนใน CWE Top 25CWE Top 25 2025cwe_top25_2025#x1.p73
การใช้ทรัพยากรอย่างไม่มีการควบคุมเป็นจุดอ่อนใน CWE Top 25CWE Top 25 2025 (CWE-400) — รายการจุดอ่อนcwe_top25_2025#x19.x2.p2
การป้องกันขอบเขตแบบปฏิเสธโดยค่าเริ่มต้น (อนุญาตเป็นข้อยกเว้น)NIST SP 800-53 Rev 5 SC-7 — การป้องกันขอบเขตSC-7
การปฏิเสธการเรียกไปยังปลายทางใด ๆ ที่ระดับเครือข่ายเป็นการควบคุม SSRF ที่แข็งแกร่งOWASP Cheat Sheet Series (SSRF Prevention §Network layer) — ชุดเอกสารแนวทางowasp_cheatsheet_series#x132.x2
ปกป้ององค์ประกอบที่ดึง URL ไม่ให้ถูก SSRFOWASP Cheat Sheet Series — ชุดเอกสารแนวทาง§ (SSRF prevention, URL-fetch tools — เครื่องมือดึง URL)
แยกการเรนเดอร์เนื้อหาที่ไม่น่าเชื่อถือ ใช้สิทธิ์น้อยที่สุดOWASP ASVS 5.0§ (sandbox / least privilege — แซนด์บ็อกซ์ / สิทธิ์น้อยที่สุด)
บันทึกเหตุการณ์ด้านการดำเนินงาน เก็บเพย์โหลดไว้นอกบันทึกNIST SP 800-92§ (log content guidance — แนวทางเนื้อหาบันทึก)

ดึงข้อมูลอ้างอิงผ่านเอนจินการปฏิบัติตามข้อกำหนดของ NextPDF (corpus manifest 1d05b7c4…d790b6) ข้อความของข้อกำหนดในที่นี้เป็นการถอดความ และไม่มีการอ้างคำต่อคำ

ภัยคุกคามการควบคุมความเสี่ยงที่เหลืออยู่
SSRF ผ่านทรัพยากรย่อยระยะไกลCSP default-src 'none' + CDP setBlockedURLs('*')จุดบกพร่องของเอนจิน Chrome ที่ข้ามผ่านกำแพงทั้งสองชั้น (การป้องกันเชิงลึกลดความเสี่ยง แต่ไม่ได้ขจัดความเสี่ยงนั้นออกไป)
SSRF ผ่านการนำทางด้วย meta-refreshการตรวจสอบก่อน Chrome ปฏิเสธแท็กดังกล่าวเวกเตอร์การนำทางใหม่ที่ไม่ตรงกับรูปแบบ
การใช้ทรัพยากรจนหมดขนาดอินพุต + เพดาน base64 + การหมดเวลา + การรีสตาร์ตทุก 100 การเรนเดอร์ไม่มีโควตาระดับโฮสต์ ต้องใช้ร่วมกับ cgroup/ulimit
การถูกบุกรุกของกระบวนการตัวเรนเดอร์แซนด์บ็อกซ์ Chrome เมื่อเปิดใช้งานnoSandbox: true นำการควบคุมนี้ออกทั้งหมด
การหลุดออกจากสไตล์ / การฉีดโค้ด</style> ที่ถูกลบใน defaultCss; CSP บล็อกสคริปต์การฉีดโค้ดผ่านเวกเตอร์ในอนาคตที่ไม่ถูกลบออก

Bridge ไม่ได้ทำงานด้านการเข้ารหัสลับใด ๆ Bridge สร้างไบต์ PDF ผ่าน Chrome และฝังไบต์ดังกล่าว การลงนาม การเข้ารหัสลับ และพฤติกรรมในโหมด Federal Information Processing Standards (FIPS) เป็นเรื่องของ core/Premium และไม่ได้รับผลกระทบจาก Artisan

  • /integrations/artisan/configuration/ — การกำหนดค่า
  • /integrations/artisan/chrome-renderer-setup/ — การตั้งค่าตัวเรนเดอร์ Chrome
  • /integrations/artisan/troubleshooting/ — การแก้ไขปัญหา
  • /integrations/artisan/production-usage/ — การใช้งานในการผลิตจริง
  • /integrations/artisan/overview/ — ภาพรวม