💡 ตัวอย่างการทำงานเต็มที่มีบน GitHub:
skip-external-resources-when-signing-dotnet

บทนำ

เอกสาร Word สามารถมีรูปภาพที่ไม่ได้อยู่ในไฟล์ได้ เอกสารจะเก็บที่อยู่ไว้และโปรแกรมใดที่เปิดมันก็จะดึงที่อยู่นั้นมา ในเครื่องเดสก์ท็อปนี่เป็นฟีเจอร์ – รูปภาพจะอัปเดตเมื่อแหล่งที่มามีการเปลี่ยนแปลง แต่บนเซิร์ฟเวอร์ที่รับอัปโหลด หมายความว่าผู้ที่ส่งไฟล์ให้คุณจะกำหนด URL ที่โครงสร้างพื้นฐานของคุณต้องร้องขอ

การโหลดเอกสารอย่างปลอดภัยเป็นพฤติกรรมของ GroupDocs.Signature สำหรับ .NET ที่ปฏิเสธการทำคำขอเหล่านั้น ตั้งแต่เวอร์ชัน 26.9, LoadOptions.SkipExternalResources มีค่าเริ่มต้นเป็น true บทความนี้เปรียบเทียบโหมดการโหลดสามแบบกับเอกสารเดียวกัน แสดงวิธีการอนุญาตโฮสต์หนึ่งโดยไม่อนุญาตทั้งหมด และอธิบายว่าทำไมการลงนามไฟล์ที่ไม่เชื่อถือจึงไม่ต้องการการเข้าถึงเครือข่ายเลย

ทำไมเรื่องนี้ถึงสำคัญกว่าที่คิด

การโจมตีนี้มีชื่อ – server‑side request forgery (SSRF) – และมีรูปแบบที่เป็นรูปธรรมสามแบบ

  • ที่อยู่ภายในที่ไม่สามารถเข้าถึงจากอินเทอร์เน็ตได้ แต่สามารถเข้าถึงจากเซิร์ฟเวอร์ของคุณได้ ดังนั้นเอกสารที่จัดทำขึ้นอย่างประณีตอาจทำให้บริการของคุณดึง http://169.254.169.254/ หรือ endpoint ของผู้ดูแลระบบบน localhost และขึ้นอยู่กับว่าคุณทำอะไรกับผลลัพธ์นั้น อาจทำให้ข้อมูลรั่วไหล
  • เส้นทาง UNC ในเอกสารอาจทำให้โฮสต์ Windows ทำการยืนยันตัวตนออกไป ส่งข้อมูลประจำตัวให้กับเซิร์ฟเวอร์ที่ผู้โจมตีควบคุม
  • ลิงก์ไปยังโฮสต์ที่ไม่ตอบสนองเลยทำให้เธรดการโหลดค้างจนหมดเวลา ซึ่งเป็นวิธีราคาถูกในการทำให้พูลของ worker หมด

ฉันเคยคิดว่านี่เป็นความกังวลเชิงทฤษฎีจนกระทั่งได้เห็นเอกสารทดสอบดึงรูปภาพผ่านบริการที่ไม่มีหน้าที่ทำการร้องขอออกไปเลย สิ่งเหล่านี้ไม่ต้องอาศัยบั๊กในไลบรารีเอกสาร การตามลิงก์เป็นสิ่งที่ฟอร์แมตต้องการ; คำถามคือเซิร์ฟเวอร์ของคุณควรทำตามหรือไม่

วิธีที่ 1 – ค่าเริ่มต้นใหม่

ไม่มี LoadOptions เลย:

using var signature = new Signature(sourcePath);
return SavePagePreview(signature, previewPath);

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

ฟีเจอร์ใดบ้างที่ถือเป็นภายนอก? รูปภาพที่เชื่อมโยงแทนที่ฝังไว้, ฟิลด์ INCLUDEPICTURE, รูปภาพที่เชื่อมโยงในงานนำเสนอและสเปรดชีต, รวมถึงรูปภาพและสไตล์ชีตที่ SVG อ้างอิง เนื้อหาที่ฝังไว้จะไม่ถูกกระทบ – มันอยู่แล้วในไฟล์

วิธีที่ 2 – รายการขาวที่อยู่หนึ่ง

เอกสารจำนวนมากเชื่อมโยงไปยังแหล่งที่เชื่อถือได้: CDN ของบริษัท, เซิร์ฟเวอร์รูปภาพภายใน, ร้านเทมเพลต ให้อนุญาตเฉพาะนั้นและไม่มีอื่น:

var loadOptions = new LoadOptions
{
    WhitelistedResources = new List<string> { trustedAddress }
};

using var signature = new Signature(sourcePath, loadOptions);

กฎการจับคู่ต้องให้ความสนใจ มันเป็นการทดสอบ substring ที่ไม่สนใจตัวพิมพ์ใหญ่‑เล็กต่อที่อยู่ทรัพยากร ซึ่งหมายความว่า fragment สั้น ๆ อาจเป็นอันตราย: github จะตรงกับ github.attacker.example/payload.png เช่นเดียวกับโฮสต์ที่คุณตั้งใจใช้ ใช้ scheme, host และ path – ตัวอย่าง whitelist คือ raw.githubusercontent.com/groupdocs-signature/

วิธีที่ 3 – อนุญาตทั้งหมด

พฤติกรรมก่อนเวอร์ชัน 26.9 ที่ยังคงใช้ได้:

var loadOptions = new LoadOptions { SkipExternalResources = false };

เหมาะสมกับเอกสารที่แอปของคุณเองสร้างขึ้น มีกับดักหนึ่งที่ควรระบุ: property LoadExternalResources ที่ล้าสมัยมี polarity ตรงกันข้าม ดังนั้น SkipExternalResources = false คือสิ่งที่แทนที่ LoadExternalResources = true หากคัดลอกค่าจาก property เก่าไปโดยไม่ระวัง คุณจะสลับท่าทีความปลอดภัยของคุณโดยไม่มีข้อผิดพลาดบอกเตือน

การเปรียบเทียบสามวิธี: ควรใช้เมื่อใด

โหมด เหมาะสำหรับ ข้อได้เปรียบหลัก ข้อจำกัด
ค่าเริ่มต้น (skip) การอัปโหลดโดยผู้ใช้, อีเมล, ไฟล์จากพันธมิตร ไม่สามารถทำคำขอออกไปได้ รูปภาพที่เชื่อมโยงจะแสดงเป็นที่ว่างเปล่า
Whitelist เอกสารที่เชื่อมโยงไปยังโฮสต์ของคุณเอง ทำให้ลิงก์ที่ถูกต้องทำงานได้ การจับคู่ substring ต้องการ fragment ที่ยาวและเฉพาะเจาะจง
Allow all ไฟล์ที่ระบบของคุณสร้างขึ้น ตัวอย่างภาพแสดงผลเหมือนเดิม คืนสภาพการเปิดเผย SSRF ที่ค่าเริ่มต้นได้ลบออก

การลงนามต้องการทรัพยากรหรือไม่?

ไม่ต้อง และนี่คือผลตอบแทนเชิงปฏิบัติ การลงนามด้วย QR‑code ทำด้วยการตั้งค่าโหลดเริ่มต้นและไม่มีการร้องขอทรัพยากรภายนอกขณะโหลด, ลงนาม หรือบันทึกเอกสาร:

var options = new QrCodeSignOptions("Approved by GroupDocs.Signature")
{
    EncodeType = QrCodeTypes.QR,
    Left = 400,
    Top = 50,
    Width = 120,
    Height = 120
};

SignResult result = signature.Sign(outputPath, options);

ผลลัพธ์ที่ลงนามยังคงเก็บลิงก์ไว้ ดังนั้นผู้ใช้ที่เปิดเอกสารในภายหลังจะยังเห็นรูปภาพที่แก้ไขบนเครื่องของตนเอง การข้ามเป็นนโยบายระดับเซิร์ฟเวอร์ ไม่ได้แก้ไขเอกสาร – ซึ่งทำให้ปลอดภัยต่อการประมวลผลไฟล์ในนามของผู้อื่น

สิ่งที่เปลี่ยนเมื่อคุณอัปเกรด

สำหรับบริการส่วนใหญ่ จะไม่มีอะไรที่มองเห็นได้ในครั้งแรก และนั่นเป็นสิ่งที่ควรบอกอย่างชัดเจน เพราะค่าเริ่มต้นด้านความปลอดภัยที่เปลี่ยนพฤติกรรมทั่วทุกที่จะไม่ผ่านการตรวจสอบอัปเกรดได้ ยกเว้นกรณีที่ตัวอย่างหรือ thumbnail เคยแสดงรูปภาพที่เชื่อมโยงและตอนนี้แสดงที่ว่างเปล่า; นั่นคือการเปลี่ยนแปลงที่ทำหน้าที่ของมัน และการแก้คือเพิ่มรายการ whitelist หากโฮสต์เป็นของคุณ, หรือยอมรับหากเอกสารมาจากภายนอก

วิธีตรวจสอบที่ตรงไปตรงมาคือวิธีที่ตัวอย่างใช้: แสดงเอกสารเดียวกันในสามโหมดและเปรียบเทียบขนาดผลลัพธ์ หากขนาดของ preview ค่าเริ่มต้นและ whitelist เหมือนกัน แสดงว่าไม่มีการดึงข้อมูลในกรณีใดกรณีหนึ่ง – ซึ่งมักหมายความว่าโฮสต์ไม่สามารถเข้าถึงจากเครื่องนั้น แทนที่ whitelist ล้มเหลว และตัวอย่างจะแสดงข้อความบอกอย่างนั้น

ตัวช่วยสร้าง Preview, เนื่องจากไม่ชัดเจน

สองในสามโหมดข้างต้นเรียกตัวช่วยขนาดเล็ก และคุ้มค่าที่จะแสดงเพราะ PreviewOptions ไม่รับพาธ:

var previewOptions = new PreviewOptions(
    pageData => File.Create(previewPath),
    (pageData, pageStream) => pageStream.Dispose())
{
    PreviewFormat = PreviewOptions.PreviewFormats.PNG
};

signature.GeneratePreview(previewOptions);

มันรับ factory สองตัว – หนึ่งสร้างสตรีมต่อหน้า, อีกหนึ่งปล่อยสตรีม ตัวอย่างเอกสารมีหน้าเดียว ดังนั้นไฟล์เดียวจะถูกเขียน; หากเป็นอินพุตหลายหน้า ให้ใส่หมายเลขหน้าในชื่อไฟล์ มิฉะนั้นทุกหน้าจะเขียนทับหน้าเดิม

แนวปฏิบัติที่ดีที่สุด

  • ถือทุกอย่างที่คุณไม่ได้สร้างเองว่าไม่เชื่อถือ, รวมถึงไฟล์จากพันธมิตรที่มีท่าทีด้านความปลอดภัยดี
  • ทำให้ fragment ของ whitelist ยาวพอที่จะไม่มีความคลุมเครือ, และตรวจสอบเมื่อ CDN มีการเปลี่ยนแปลง
  • อย่าตั้งค่า SkipExternalResources จากค่าที่เคยกำหนดให้ LoadExternalResources
  • ตรวจสอบด้วยขนาดผลลัพธ์แทนการตรวจสอบค่าการตั้งค่า; การตั้งค่าที่ดูถูกต้องและคำขอที่ไม่ได้เกิดขึ้นเป็นข้ออ้างที่ต่างกัน

สิ่งที่เกิดขึ้นกับ SVG

ควรอธิบายแยกต่างหาก เพราะ SVG เป็นฟอร์แมตอัปโหลดที่พบบ่อยและเป็นเวกเตอร์ SSRF ที่พบบ่อย SVG สามารถอ้างอิงรูปภาพและสไตล์ชีตด้วย URL, และการอ้างอิงเหล่านั้นเป็นทรัพยากรภายนอกตามกฎเดียวกัน – ถูกข้ามโดยค่าเริ่มต้น, สามารถ whitelist ได้, สามารถกู้คืนได้ บริการที่รับ SVG avatar หรือโลโก้และเรนเดอร์บนเซิร์ฟเวอร์เป็นระบบที่การเปลี่ยนแปลงนี้คุ้มครองโดยตรง

หาก pipeline ของคุณรับ SVG จากผู้ใช้, ค่าเริ่มต้นคือการตั้งค่าที่คุณต้องการ, และ whitelist ใช้ในกรณีที่เทมเพลตของคุณดึงสไตล์ชีตที่แชร์จากโฮสต์ที่คุณดูแล

สรุป

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

แหล่งข้อมูลเพิ่มเติม