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

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

บริดจ์นี้ส่ง Hypertext Markup Language (HTML) ของคุณข้ามขอบเขตเครือข่ายไปยังเอนจินเบราว์เซอร์ หน้านี้บันทึกการควบคุมที่ใช้ปกป้องขอบเขตดังกล่าว โดยยึดซอร์สโค้ดเป็นแหล่งอ้างอิงที่ถูกต้อง เมื่อการควบคุมใดอ้างอิงมาตรฐาน การอ้างอิงนั้นคือรายการที่ประกาศไว้ใน docblock ของโค้ด หน้านี้กล่าวซ้ำข้อยืนยันของโค้ดเท่านั้น ไม่ได้สร้างถ้อยคำเชิงบรรทัดฐานขึ้นใหม่

docblock ของแพ็กเกจระบุภัยคุกคามที่แพ็กเกจป้องกันไว้ดังนี้

  • XSS-to-PDF — Cross-site scripting (XSS) ผ่านมาร์กอัปอันตรายที่ทำงานระหว่างการเรนเดอร์ Portable Document Format (PDF)
  • SSRF — Server-side request forgery (SSRF) ที่เกิดจากมาร์กอัปหรือ Uniform Resource Locator (URL) ปลายทางที่ส่งคำขอไปยังที่อยู่ภายใน
  • Resource exhaustion — อินพุตขนาดใหญ่เกินไปหรือ decompression bomb
  • DNS rebinding — Domain Name System (DNS) rebinding ซึ่งชื่อโฮสต์ผ่านการตรวจสอบความถูกต้องแล้ว แต่รีโซลฟ์เป็นที่อยู่ส่วนตัว ณ เวลาที่เชื่อมต่อ
  • On-path TLS interception — การดักจับ Transport Layer Security (TLS) แบบ on-path ผ่านใบรับรองที่ถูกสับเปลี่ยนระหว่างเส้นทางไปยัง Worker

ภัยคุกคามแต่ละรายการมีการควบคุมเฉพาะที่ทดสอบได้ตามรายละเอียดด้านล่าง

CloudflareSecurityPolicy::validate() ทำงานก่อนสร้างคำขอใดๆ

การควบคุมพฤติกรรมแหล่งที่มาของขีดจำกัด
ขีดจำกัดขนาดปฏิเสธ HTML ที่ใหญ่กว่า maxHtmlSizeCloudflareRendererConfig ค่าเริ่มต้น 5000000 ไบต์
การป้องกัน decompression bomb แบบ Base64ประเมินขนาดหลังถอดรหัสของ URI แบบ data:…;base64,… ทุกค่า และปฏิเสธค่าที่เท่ากับหรือสูงกว่าเพดานMAX_DATA_URI_BYTES = 13631488
การห้าม meta-refreshปฏิเสธ <meta http-equiv="refresh"> ใดๆ โดยไม่คำนึงถึงตัวพิมพ์ใหญ่หรือเล็กregex ใน CloudflareSecurityPolicy

การละเมิดจะทำให้เกิด RuntimeException พร้อมข้อความที่ระบุค่าที่ไม่ถูกต้องและขีดจำกัด การห้าม meta-refresh มีไว้เพราะ refresh directive สามารถเริ่มการนำทางภายในหน้าที่ Worker เรนเดอร์ได้ จึงเป็นช่องทาง SSRF ที่อยู่ในเนื้อหา ไม่ใช่ใน URL

นโยบายความปลอดภัย HTML จาก nextpdf/core (HtmlSecurityPolicyInterface ค่าเริ่มต้น DefaultHtmlSecurityPolicy) ทำงานในชั้นการแยกวิเคราะห์และเสริมการตรวจสอบในชั้น transport ข้างต้น สามารถเรียกนโยบายนี้ด้วย getHtmlSecurityPolicy() และฉีดนโยบายแบบกำหนดเองผ่าน constructor ได้

CloudflareSecurityPolicy::validateWorkerUrl():

  1. ปฏิเสธ URL ที่แยกวิเคราะห์ไม่ได้หรือไม่มี scheme/host (Invalid Worker URL)
  2. ปฏิเสธ scheme ใดๆ ที่ไม่ใช่ HTTPS (Worker URL must use HTTPS)
  3. สำหรับโฮสต์ที่เป็น IP literal จะปฏิเสธช่วงส่วนตัวหรือช่วงสงวนด้วย FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ของ PHP ในทางปฏิบัติ การตรวจสอบนี้ปฏิเสธพื้นที่ส่วนตัวตาม RFC 1918, loopback และที่อยู่ link-local ตาม RFC 3927 การทดสอบครอบคลุมการปฏิเสธ 192.168.x, 127.0.0.1 และ 169.254.x ไว้อย่างชัดเจน ส่วนขยาย filter ของ PHP เป็นผู้ตัดสินว่าอยู่ในช่วงใด แพ็กเกจนี้ไม่ได้ผูกการตัดสินดังกล่าวไว้กับข้อกำหนดใด RFC 1918 และ RFC 3927 ถูกกล่าวถึงที่นี่เชิงพรรณนาในฐานะนิยามที่เป็นที่รู้จักของช่วงเหล่านั้น
  4. สำหรับชื่อโฮสต์ จะรีโซลฟ์เรกคอร์ด A และ AAAA ทั้งหมด ด้วย dns_get_record() (ไม่ใช่ gethostbyname() ซึ่งคืนเฉพาะคำตอบแรกเท่านั้น) และปฏิเสธโฮสต์หากมีที่อยู่ใดในผลลัพธ์ที่เป็นส่วนตัวหรือสงวนไว้

การรีโซลฟ์เรกคอร์ดทั้งหมดเป็นพฤติกรรมที่ตั้งใจไว้ docblock ของคลาสบันทึกว่าเป็นการป้องกันโฮสต์ที่คืนหลายเรกคอร์ด เพราะการค้นหาแบบเรกคอร์ดเดียวอาจเลือกที่อยู่สาธารณะ แต่การเชื่อมต่อภายหลังอาจเลือกที่อยู่ส่วนตัว แนวทางนี้สอดคล้องกับ OWASP SSRF Prevention Cheat Sheet กล่าวคือ รีโซลฟ์คำตอบ A และ AAAA ทั้งหมดของโดเมน และตรวจหาที่อยู่ที่ไม่ใช่สาธารณะในชุดผลลัพธ์ทั้งหมด

validateWorkerUrl() คืนชุด IP ที่ผ่านการตรวจสอบแล้ว ทันทีก่อนส่งคำขอ ตัวเรนเดอร์จะเรียก assertPinsStillValid() การเรียกนี้จะรีโซลฟ์โฮสต์ใหม่และปฏิเสธ IP ใหม่ที่พบ (Worker URL DNS answer changed since validation — possible DNS rebinding attack) เพื่อปิดช่องว่าง time-of-check / time-of-use ระหว่างการตรวจสอบความถูกต้องกับการเชื่อมต่อ

เมื่อมีชุด IP ที่ผ่านการตรวจสอบแล้วหรือชุดหมุด Subject Public Key Info (SPKI) และ มีการจัดเตรียม PHP Standards Recommendation 17 (PSR-17) ResponseFactory ไว้ ตัวเรนเดอร์จะใช้ Transport\PinnedCurlTransport แทนไคลเอนต์ PHP Standards Recommendation 18 (PSR-18) ที่ฉีดเข้ามา transport จะบังคับใช้การควบคุมเหล่านี้ที่ชั้น cURL handle ดังนี้

  • Pinned DNSCURLOPT_RESOLVE ผูก host:port ไว้กับชุด IP ที่ผ่านการตรวจสอบแล้ว ดังนั้น libcurl จึงไม่ค้นหาเอง ณ เวลาที่เชื่อมต่อ การผูกนี้ทำให้การตรวจสอบ DNS ในระดับ userland มีผลกับการเชื่อมต่อจริง หากไม่มีการผูกนี้ libcurl อาจรีโซลฟ์ไปยังที่อยู่อื่นได้
  • การปักหมุดคีย์สาธารณะ TLSCURLOPT_PINNEDPUBLICKEY ตั้งค่าจากชุดหมุดที่รวมกันแล้ว แนวทางนี้เป็นไปตาม RFC 7469 §2.6 กล่าวคือ การเชื่อมต่อแบบปักหมุดจะได้รับการยอมรับเมื่อชุดลายนิ้วมือ SPKI ที่เซิร์ฟเวอร์นำเสนอมีค่าร่วมกับชุดหมุดที่กำหนดค่าไว้ และความล้มเหลวในการตรวจสอบหมุดถือเป็นข้อผิดพลาดที่กู้คืนไม่ได้ สตริงหมุดจะถูกปรับให้อยู่ในรูปแบบมาตรฐานจาก sha256/<base64> เป็นรูปแบบ sha256//<base64> ของ cURL หมุดที่มีรูปแบบไม่ถูกต้องจะทำให้เกิดข้อยกเว้น InvalidSpkiPinException
  • เปิดใช้การตรวจสอบ TLSCURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2 ถูกตั้งค่าไว้
  • ไม่มีการเปลี่ยนเส้นทางอัตโนมัติCURLOPT_FOLLOWLOCATION => false, CURLOPT_MAXREDIRS => 0 การตอบกลับ 3xx จะถูกส่งต่อไปยังชั้นนโยบายแทนที่จะให้ libcurl ติดตามไปยังโฮสต์ที่ยังไม่ผ่านการตรวจสอบ docblock ของคลาสระบุว่านี่เป็นพฤติกรรมที่ตั้งใจไว้ การเปลี่ยนเส้นทางจึงต้องผ่านการตรวจสอบความถูกต้องใหม่แทนการติดตามอย่างเงียบๆ
  • Hard timeoutCURLOPT_TIMEOUT ตั้งค่าจาก renderTimeout (ค่าเริ่มต้น 30 วินาที)

ข้อผิดพลาดของ cURL หรือ body ที่ไม่ใช่สตริงจะทำให้เกิด CloudflareRenderException พร้อมหมายเลขข้อผิดพลาดและข้อความของ cURL

การกำหนดค่ามี pinnedPublicKeys และ backupPublicKeys แยกจากกัน RFC 7469 §2.5 อธิบายหมุดสำรองว่าเป็นลายนิ้วมือของคู่คีย์สำรองที่ยังไม่ได้ดีพลอยและเก็บแบบออฟไลน์ และถือเป็นเส้นทางกู้คืนหลักเมื่อการตรวจสอบหมุดล้มเหลวโดยไม่ตั้งใจ ให้เก็บหมุดสำรองไว้อย่างน้อยหนึ่งรายการ เพื่อไม่ให้การหมุนเวียนใบรับรองทำให้ endpoint ใช้งานไม่ได้ ฟิลด์ที่แยกกันนี้ช่วยให้คุณตรวจสอบการหมุนเวียนได้อย่างเป็นอิสระ ในทางปฏิบัติ

  • ปักหมุด SPKI ของใบรับรอง leaf หรือของ intermediate ที่คุณควบคุมการหมุนเวียนได้
  • กำหนดค่าหมุดสำรองสำหรับใบรับรองถัดไปเสมอก่อนทำการหมุนเวียน
  • ชุดหมุดว่างจะปิดใช้งานการปักหมุด ให้ใช้ตัวเลือกนั้นเฉพาะกับห่วงโซ่ใบรับรองที่เสถียรและเป็นที่รู้จักเท่านั้น การปักหมุดเป็นแบบ opt-in ผ่านการกำหนดค่า
  • คำขอ Worker ส่ง Authorization: Bearer <apiToken> apiToken เป็น #[SensitiveParameter] ดังนั้น stack trace จึงปกปิดค่านี้ไว้ การตรวจสอบความสามารถในการเข้าถึงจะส่ง bearer header เดียวกันด้วยเมท็อด HEAD ของ Hypertext Transfer Protocol (HTTP)
  • access key ของ Cloudflare R2 (accessKeyId, secretAccessKey) เป็น #[SensitiveParameter] และใช้เฉพาะเพื่อสร้างคีย์ลงนาม Amazon Web Services (AWS) Signature V4 เท่านั้น
  • ApiKeyValidator เปรียบเทียบคีย์ด้วย hash_equals() (timing-safe) และรองรับการจัดเก็บคีย์ที่แฮชด้วย Secure Hash Algorithm 256 (SHA-256) โดยเรียกผ่าน validateHashed() ด้วย
  • อ็อบเจกต์การกำหนดค่าเป็น final readonly — ความลับเมื่อตั้งค่าแล้วจะเปลี่ยนแปลงไม่ได้
  • จัดหาความลับผ่านตัวแปรสภาพแวดล้อมหรือ secrets manager และอย่าคอมมิตความลับเหล่านั้นเด็ดขาด แพ็กเกจนี้ยึดตามแนวปฏิบัติความปลอดภัยพื้นฐานโดยรวมของ NextPDF ได้แก่ PHPStan Level 10, declare(strict_types=1) ในทุกไฟล์ ไม่มี eval()/exec() และ GitHub Actions ที่ปักหมุดไว้กับ SHA
  • แพ็กเกจนี้ไม่ได้ระบุขีดจำกัดของแพลตฟอร์ม Cloudflare รายการใดๆ (เวลา CPU ของ Worker หน่วยความจำ เพดานขนาด request body หรือจำนวน subrequest) ขีดจำกัดด้านขนาดและเวลาเพียงอย่างเดียวที่เอกสารนี้ระบุคือสิ่งที่แพ็กเกจบังคับใช้เอง ตามที่แสดงไว้ข้างต้นและใน /integrations/cloudflare/configuration/ สำหรับขีดจำกัดของแพลตฟอร์ม ให้อ้างอิงเอกสารทางการของ Cloudflare และการนำไปใช้งานของ Worker ของคุณเอง
  • แพ็กเกจนี้ไม่ได้ลงนาม PDF และไม่ได้กล่าวอ้างว่าลายเซ็นสอดคล้องกับข้อกำหนดใดๆ เมื่อจำเป็นต้องใช้ลายเซ็น ให้เรนเดอร์ที่นี่แล้วจึงลงนามด้วยเอนจิน NextPDF Pro รองรับเฉพาะการลงนาม PDF Advanced Electronic Signatures (PAdES) B-B เท่านั้น โปรไฟล์การตรวจสอบความถูกต้องระยะยาวเป็นความสามารถระดับ Enterprise และอยู่นอกขอบเขตของบริดจ์นี้
  • แพ็กเกจนี้ไม่ได้รับรอง ไม่รับประกัน และไม่ได้ทำให้ไปป์ไลน์เป็น “tamper-proof” แพ็กเกจนี้ใช้เฉพาะการควบคุมที่เจาะจงและตรวจสอบได้จากซอร์สโค้ดตามที่อธิบายไว้ในหน้านี้เท่านั้น
อาการสิ่งที่ควรตรวจสอบก่อน
Worker URL must use HTTPSตรวจสอบ scheme ของ workerUrl ที่กำหนดค่าไว้
private or reserved IPตรวจสอบเรกคอร์ด DNS ของชื่อโฮสต์ Worker และมองหาเรกคอร์ดที่รีโซลฟ์ไปยังพื้นที่ RFC 1918 / loopback / RFC 3927
DNS answer changed since validationตรวจหาความไม่เสถียรของ DNS หรือความพยายาม rebinding แล้วรีโซลฟ์ใหม่และตรวจสอบชุดเรกคอร์ดทั้งหมด
cURL transport errorตรวจสอบเส้นทางเครือข่าย ห่วงโซ่ TLS และหากตั้งค่าหมุดไว้ ให้ตรวจว่า SPKI ของใบรับรองที่ให้บริการยังคงอยู่ในชุดหมุดหรือไม่
การเรนเดอร์ล้มเหลวทันทีหลังการหมุนเวียนใบรับรองตรวจว่าชุดหมุดไม่มีหมุดสำรองที่ตรงกันหรือไม่ เพิ่ม SPKI ใหม่เป็นหมุดสำรอง ก่อน การหมุนเวียน
is not installed / no LocalRendererFactoryInterfaceมีการเปิดใช้งาน fallback แต่ไม่ได้เชื่อมต่อ factory หรือไม่มี nextpdf/artisan
การปฏิเสธจาก rate limit ไม่สอดคล้องกันระหว่างโหนดตัวจำกัดแบบ in-memory ทำงานแยกกันในแต่ละโปรเซส ให้วาง store ที่ใช้ร่วมกันไว้ด้านหน้า

รายงานช่องโหว่ผ่าน GitHub Security Advisories หรือผู้ติดต่อด้านความปลอดภัยที่ระบุใน SECURITY.md ของที่เก็บโค้ด อย่าส่งปัญหาด้านความปลอดภัยเป็น GitHub issue สาธารณะ

  • /integrations/cloudflare/overview/ — เหตุผลที่แพ็กเกจนี้ออกแบบโดยยึดขอบเขตเป็นหลัก
  • /integrations/cloudflare/configuration/ — ฟิลด์ชุดหมุดและขีดจำกัด
  • /integrations/cloudflare/troubleshooting/ — การแมประหว่างความล้มเหลวกับ exception แบบครบถ้วน