ความปลอดภัยและการดำเนินงาน
ภาพรวมโดยย่อ
หัวข้อที่มีชื่อว่า “ภาพรวมโดยย่อ”บริดจ์นี้ส่ง 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
ภัยคุกคามแต่ละรายการมีการควบคุมเฉพาะที่ทดสอบได้ตามรายละเอียดด้านล่าง
การควบคุมอินพุต (ก่อนที่คำขอจะออกจาก PHP)
หัวข้อที่มีชื่อว่า “การควบคุมอินพุต (ก่อนที่คำขอจะออกจาก PHP)”CloudflareSecurityPolicy::validate() ทำงานก่อนสร้างคำขอใดๆ
| การควบคุม | พฤติกรรม | แหล่งที่มาของขีดจำกัด |
|---|---|---|
| ขีดจำกัดขนาด | ปฏิเสธ HTML ที่ใหญ่กว่า maxHtmlSize | CloudflareRendererConfig ค่าเริ่มต้น 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 ได้
การควบคุมปลายทาง (SSRF และ DNS rebinding)
หัวข้อที่มีชื่อว่า “การควบคุมปลายทาง (SSRF และ DNS rebinding)”CloudflareSecurityPolicy::validateWorkerUrl():
- ปฏิเสธ URL ที่แยกวิเคราะห์ไม่ได้หรือไม่มี scheme/host (
Invalid Worker URL) - ปฏิเสธ scheme ใดๆ ที่ไม่ใช่ HTTPS (
Worker URL must use HTTPS) - สำหรับโฮสต์ที่เป็น 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 ถูกกล่าวถึงที่นี่เชิงพรรณนาในฐานะนิยามที่เป็นที่รู้จักของช่วงเหล่านั้น - สำหรับชื่อโฮสต์ จะรีโซลฟ์เรกคอร์ด 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 ระหว่างการตรวจสอบความถูกต้องกับการเชื่อมต่อ
การควบคุม transport (PinnedCurlTransport)
หัวข้อที่มีชื่อว่า “การควบคุม transport (PinnedCurlTransport)”เมื่อมีชุด 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 DNS —
CURLOPT_RESOLVEผูก host:port ไว้กับชุด IP ที่ผ่านการตรวจสอบแล้ว ดังนั้น libcurl จึงไม่ค้นหาเอง ณ เวลาที่เชื่อมต่อ การผูกนี้ทำให้การตรวจสอบ DNS ในระดับ userland มีผลกับการเชื่อมต่อจริง หากไม่มีการผูกนี้ libcurl อาจรีโซลฟ์ไปยังที่อยู่อื่นได้ - การปักหมุดคีย์สาธารณะ TLS —
CURLOPT_PINNEDPUBLICKEYตั้งค่าจากชุดหมุดที่รวมกันแล้ว แนวทางนี้เป็นไปตาม RFC 7469 §2.6 กล่าวคือ การเชื่อมต่อแบบปักหมุดจะได้รับการยอมรับเมื่อชุดลายนิ้วมือ SPKI ที่เซิร์ฟเวอร์นำเสนอมีค่าร่วมกับชุดหมุดที่กำหนดค่าไว้ และความล้มเหลวในการตรวจสอบหมุดถือเป็นข้อผิดพลาดที่กู้คืนไม่ได้ สตริงหมุดจะถูกปรับให้อยู่ในรูปแบบมาตรฐานจากsha256/<base64>เป็นรูปแบบsha256//<base64>ของ cURL หมุดที่มีรูปแบบไม่ถูกต้องจะทำให้เกิดข้อยกเว้นInvalidSpkiPinException - เปิดใช้การตรวจสอบ TLS —
CURLOPT_SSL_VERIFYPEER => true,CURLOPT_SSL_VERIFYHOST => 2ถูกตั้งค่าไว้ - ไม่มีการเปลี่ยนเส้นทางอัตโนมัติ —
CURLOPT_FOLLOWLOCATION => false,CURLOPT_MAXREDIRS => 0การตอบกลับ 3xx จะถูกส่งต่อไปยังชั้นนโยบายแทนที่จะให้ libcurl ติดตามไปยังโฮสต์ที่ยังไม่ผ่านการตรวจสอบ docblock ของคลาสระบุว่านี่เป็นพฤติกรรมที่ตั้งใจไว้ การเปลี่ยนเส้นทางจึงต้องผ่านการตรวจสอบความถูกต้องใหม่แทนการติดตามอย่างเงียบๆ - Hard timeout —
CURLOPT_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” แพ็กเกจนี้ใช้เฉพาะการควบคุมที่เจาะจงและตรวจสอบได้จากซอร์สโค้ดตามที่อธิบายไว้ในหน้านี้เท่านั้น
Runbook การดำเนินงาน
หัวข้อที่มีชื่อว่า “Runbook การดำเนินงาน”| อาการ | สิ่งที่ควรตรวจสอบก่อน |
|---|---|
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 แบบครบถ้วน