1 คะแนน โดย GN⁺ 2024-01-27 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • commit 0226b56 ของ rhboot/shim แก้ไข CVE-2023-40547 ซึ่งเกิดจากการเชื่อถือ ค่าขนาดใน HTTP header ตามที่ระบุไว้ในกระบวนการรับไฟล์
  • หาก header ที่ถูกดัดแปลงระบุขนาดเล็กกว่าข้อมูลที่รับมาจริง shim อาจจัดสรรพื้นที่เล็กกว่า buffer ที่จำเป็น
  • โค้ดเดิมใช้ค่า header สำหรับการจัดสรรหน่วยความจำ แต่ใช้ metadata ของโปรโตคอล สำหรับการคัดลอกข้อมูล ซึ่งอาจนำไปสู่ out-of-bounds write
  • แพตช์เพิ่มการตรวจสอบ *buf_size < rx_message.BodyLength ใน receive_http_response() ของ httpboot.c และเมื่อไม่ผ่านจะจัดการเป็น EFI_BAD_BUFFER_SIZE พร้อมข้อความ Invalid Content-Length
  • ขอบเขตการเปลี่ยนแปลงคือ เพิ่ม 7 บรรทัด·ลบ 1 บรรทัด ในไฟล์ httpboot.c เพียงไฟล์เดียว และแก้คำสะกดผิด Content-Lenght เป็น Content-Length ด้วย

ลำดับการเกิดช่องโหว่

  • CVE-2023-40547 เป็นปัญหาที่เกิดขึ้นเมื่อ shim ดึงไฟล์ผ่าน HTTP หรือโปรโตคอลที่เกี่ยวข้อง
  • ในกระบวนการจัดสรร buffer เพื่อเก็บข้อมูลที่รับเข้ามา มีการใช้ ค่าขนาดใน HTTP header
  • HTTP header สามารถถูกดัดแปลงได้ และอาจระบุขนาดที่เล็กกว่าข้อมูลที่รับมาจริง
  • ลำดับการทำงานเดิมใช้ค่า header สำหรับการจัดสรร buffer และเมื่อคัดลอกข้อมูลจาก rx buffer จะอ้างอิงตาม metadata ของโปรโตคอล
  • ความแตกต่างนี้ทำให้สามารถคัดลอกข้อมูลที่ใหญ่กว่า buffer ที่จัดสรรไว้ได้ และส่งผลให้เกิด out-of-bounds write

เนื้อหาของแพตช์

  • แพตช์เพิ่มการตรวจสอบเชิงป้องกันใน receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) ของ httpboot.c
  • กรณี *buf_size == 0 จะแก้คำสะกดผิดในข้อความข้อผิดพลาดเดิม แล้วไปที่ goto error
    • Failed to get Content-LenghtFailed to get Content-Length
  • การตรวจสอบใหม่จะตรวจเงื่อนไข *buf_size < rx_message.BodyLength
    • หากเงื่อนไขเป็นจริง จะตั้งค่า efi_status = EFI_BAD_BUFFER_SIZE
    • แสดงข้อผิดพลาด Invalid Content-Length
    • จากนั้นไปที่ goto error

ขอบเขตการเปลี่ยนแปลง

  • ไฟล์ที่เปลี่ยนแปลงมีเพียง httpboot.c 1 ไฟล์
  • ปริมาณการเปลี่ยนแปลงคือ เพิ่ม 7 บรรทัด, ลบ 1 บรรทัด
  • สาระสำคัญคือ logic ที่ตรวจสอบว่า rx_message.BodyLength ซึ่งเป็นความยาวของ body ที่รับเข้ามา มากกว่า *buf_size ซึ่งเป็นขนาดที่จัดสรรไว้หรือไม่

บันทึกที่เกี่ยวข้อง

  • commit นี้ถูกระบุว่าเป็นการเปลี่ยนแปลงเพื่อแก้ไข CVE-2023-40547
  • ข้อความ commit ระบุชัดเจนว่าปัญหาเกิดจากการเชื่อถือ HTTP header อย่างไม่ถูกต้อง
  • ผู้รายงานช่องโหว่ถูกบันทึกว่าเป็น Bill Demirkapi จาก Microsoft Security Response Center

1 ความคิดเห็น

 
GN⁺ 2024-01-27
ความคิดเห็นจาก Hacker News
  • shim เป็น EFI bootloader ที่มักใช้กันใน Linux distributions ที่ต้องการเปิดใช้ Secure Boot
    ในมุมของดิสโทร แทนที่จะให้ผู้ใช้ลงทะเบียนคีย์ด้วยตนเอง ก็อยากให้เปิด Secure Boot ได้ง่ายด้วยคีย์ลายเซ็นของ Microsoft ที่มีมาให้เป็นค่าเริ่มต้น
    แต่โดยทั่วไป Microsoft จะไม่เซ็นชื่อให้ bootloader แบบ GPL อย่าง GRUB ดังนั้นจึงมีการสร้าง shim ที่สามารถเซ็นด้วยคีย์ของ Microsoft ได้ขึ้นมา และ shim จะตรวจสอบลายเซ็นของสิ่งที่จะบูตด้วยคีย์แยกต่างหากชื่อ Machine Owner Key หรือ MOK
    ตอนระบุ EFI binary ที่จะให้ shim บูต สามารถส่งเป็น HTTP URL ได้ และหาก HTTP server เป็นอันตราย ก็อาจทำให้เกิดการเขียนออกนอกขอบเขตได้
    อย่างไรก็ตาม โดยปกติมักใช้เพื่อบูต local second-stage bootloader อย่าง GRUB จึงดูมีโอกาสน้อยที่จะเป็นปัญหาในระบบติดตั้งส่วนใหญ่
    Secure Boot ถูกออกแบบมาตั้งแต่แรกให้สามารถเพิกถอนแม้แต่ไบนารีที่ถูกเซ็นชื่อแล้วผ่าน รายการ DBX ได้ และเมื่อใส่รายการนี้ลงใน UEFI ก็จะปฏิเสธไบนารีนั้นแม้จะมีลายเซ็นที่ถูกต้องก็ตาม
    หากเพิ่มลายเซ็นของไบนารี shim รุ่นเก่าที่มีบั๊กนี้เข้าไปในรายการ แต่ละคนก็อัปเดตรายการบนอุปกรณ์ของตนได้ อาจกระจายผ่าน capsule update อย่าง LVFS ได้เช่นกัน และถ้าจัดการ Secure Boot keys และ variables ด้วยตนเอง ก็สามารถดาวน์โหลดรายการจาก https://uefi.org/revocationlistfile มาลงทะเบียนได้

    • ผมเป็นผู้ค้นพบบั๊กในโพสต์ต้นฉบับเอง และความเข้าใจผิดที่พบบ่อยคือคิดว่าประเด็นนี้ ถูกใช้โจมตีได้เฉพาะตอนใช้ HTTP boot เท่านั้น
      ถ้าเป็นแบบนั้นจริงก็คงไม่ได้ระดับ Critical
      บั๊กนี้สามารถถูกใช้โจมตีได้เมื่อมัลแวร์ที่มีสิทธิ์ระดับสูงในเครื่องเขียนทับ EFI partition จากฝั่ง local, เมื่อมีการโจมตีแบบ man-in-the-middle จากเครือข่ายใกล้เคียงที่เปิด PXE boot, และเมื่อใช้ HTTP boot ก็ถูกใช้โจมตีแบบ remote man-in-the-middle ได้
      ผู้โจมตีจากระยะไกลที่ไม่มีสิทธิ์แต่ยืนอยู่ในตำแหน่ง man-in-the-middle และเครื่องเป้าหมายใช้ HTTP boot สามารถใช้ประโยชน์ได้โดยไม่ต้องเข้าถึงเครื่องโดยตรง
      ผู้โจมตีจากระยะไกลที่ได้สิทธิ์และสามารถรันโค้ดบนเครื่องเป้าหมายได้ สามารถ bypass Secure Boot ได้แม้เหยื่อจะไม่ได้ใช้ HTTP boot ตราบใดที่ firmware รองรับ HTTP
      ตัวอย่างเช่น เปลี่ยนตัวแปรลำดับการบูตให้ชี้ไปยัง server ที่ผู้โจมตีควบคุม หรือเขียนทับ bootloader ใน EFI partition ด้วยภาพ shim และ GRUB2 ปกติ แล้วทำให้ grub.cfg chainload shim ตัวใหม่ผ่าน HTTP
      เพราะไวยากรณ์อุปกรณ์ของ GRUB2 สามารถระบุอุปกรณ์ที่รองรับได้รวมถึง HTTP
      นอกจากนี้ หากผู้โจมตีใกล้เคียงที่ไม่มีสิทธิ์อยู่ในตำแหน่ง man-in-the-middle และเครื่องเป้าหมายใช้ PXE boot ก็สามารถเชื่อมลำดับแบบ shim ของ PXE → GRUB2 ของ PXE → shim บน HTTP เพื่อใช้โจมตีได้
    • Microsoft ไม่เซ็นชื่อให้ GRUB ซึ่งเป็น bootloader ภายใต้ไลเซนส์ GPLv3 เพราะฝ่ายกฎหมายของ Microsoft มองว่า GPLv3 อาจทำให้เกิดสิทธิ์ในการบังคับให้ส่งมอบ signing key แก่นักพัฒนาได้
      ที่มา: https://techcommunity.microsoft.com/t5/hardware-dev-center/u...
    • สงสัยมานานว่าทำไม bootloader ต้องสื่อสารผ่านเครือข่ายด้วย แต่พอมีคำอธิบายว่าสามารถระบุ EFI binary เป็น HTTP URL ได้ก็เข้าใจแล้ว
    • สงสัยว่า shim หลีกเลี่ยงปัญหาที่ Microsoft กังวลได้อย่างไร
      ถ้าเงื่อนไข anti-Tivoization ของ GPLv3 อาจบังคับให้ต้องให้ Secure Boot signing key งั้นก็ไม่ใช่ว่าควรต้องให้ MOK signing key เมื่อมีการร้องขอด้วยหรือ
      ถ้าอย่างนั้นใคร ๆ ก็จะได้คีย์ที่สามารถเซ็นโค้ดใดก็ได้ให้ถูกบูตทางอ้อมผ่าน Secure Boot ซึ่งไม่แน่ใจว่าต่างอย่างมีนัยสำคัญจากการที่ Microsoft ออก signing key ให้โครงการ GPLv3 อย่าง GRUB โดยตรงหรือไม่
    • ถ้าจุดประสงค์คือบูต local second-stage bootloader ก็อดคิดไม่ได้ว่า Windows Boot Manager น่าจะทำหน้าที่เดียวกันได้ไม่ใช่หรือ
      บนเครื่อง BIOS รุ่นเก่าเคยตั้งให้ WBM chainload GRUB ไว้ แต่บนเครื่อง UEFI ยังไม่เคยลอง จึงไม่แน่ใจว่ามีจุดไหนที่ทำไม่ได้หรือไม่
  • อาจมีคนสงสัยว่า “ทำไมถึงบูตจากเซิร์ฟเวอร์ที่ไม่น่าเชื่อถือหรือถูกเจาะ?”, “ถ้าเซิร์ฟเวอร์ถูกเจาะอยู่แล้ว ส่งไบนารีอันตรายมาเลยก็ได้ แบบนี้ไม่ไร้ความหมายหรือ?” คำตอบสั้น ๆ คือไบนารีที่ shim จะบูตเป็นตัวสุดท้ายต้อง ถูกเซ็นด้วย MOK
    ดังนั้นไม่ว่าจะบูตภายในเครือข่ายที่ถูกเจาะ, บูตผ่าน HTTP, หรือบูตจากเซิร์ฟเวอร์ที่ถูกเจาะ ก็ควรคงหลักประกันด้านความปลอดภัยแบบเดียวกันไว้ ไม่เกี่ยวว่าจะใช้ HTTPS หรือไม่
    Secure Boot ไม่ได้ป้องกันการ downgrade อยู่แล้ว ดังนั้นการที่เซิร์ฟเวอร์ที่ถูกเจาะสามารถถูกใช้ทำ downgrade attack ได้จึงเป็นคนละประเด็นกับช่องโหว่นี้
    การป้องกัน downgrade attack ก็ต้องไปทำแยกด้วยวิธีที่แข็งแรงกว่านี้อยู่ดี
    แต่ก็ยังไม่แน่ใจว่าทำไม shim ถึงต้องรองรับ HTTP boot ได้โดยตรง
    น่าจะให้ local EFI binary ตัวที่สองซึ่งเซ็นด้วย MOK จัดการแทนก็ได้ แต่คงคิดว่าเป็นฟีเจอร์ที่ทำได้ค่อนข้างง่าย

  • ไม่เข้าใจว่าทำไมโค้ดนี้ถึงจัดการความยาวของเนื้อหาด้วยเกณฑ์สองแบบ
    ตาม RFC ใน HTTP/1.1 ค่า Content-Length คือข้อมูลที่เชื่อถือได้สำหรับความยาวของเนื้อความในคำขอ/คำตอบ HTTP
    ข้อมูลที่อยู่บนสายเกินกว่าความยาวนั้น ตามนิยามแล้วถือเป็นส่วนหนึ่งของข้อความอื่น
    ในทางกลับกัน ถ้า Content-Length มากกว่า rx_message.BodyLength ก็แปลว่ายังรับข้อความมาไม่ครบ จึงควรรอต่อหรือคืนข้อผิดพลาดหมดเวลา
    ไม่ว่าแบบไหน ถ้าไม่มีการรับประกันว่า rx_message.BodyLength เท่ากับ Content-Length ก็แปลว่าค่านั้นไม่ถูกต้อง
    ถ้าอยากจัดการแบบผ่อนปรนกว่านี้ ก็ไม่มีเหตุผลต้องดูเฮดเดอร์ Content-Length เลย และใช้ rx_message.BodyLength เป็นขนาดบัฟเฟอร์ ตีความข้อมูลทั้งหมดบนสายว่าเป็นข้อความที่รับมาได้เลย
    โค้ดปัจจุบันซับซ้อนเกินจำเป็น และบั๊กแบบนี้ก็เลยหลุดเข้ามา

    • ถ้าดูเฉพาะ commit นี้อย่างเดียวจะเข้าใจผิดได้ง่าย
      เมื่อดูโค้ดรอบข้าง https://github.com/rhboot/shim/blob/0226b56513b2b8bd5fd281bc... จะเห็นว่าภายในลูปมีการรับข้อมูลเป็นชิ้น ๆ และทุกครั้งจะตรวจว่าข้อมูลใหม่ทำให้เกินความจุบัฟเฟอร์ที่กำหนดโดย Content-Length หรือไม่
      แต่ก่อนหน้านี้ไม่ได้ตรวจสำหรับ การอ่านครั้งแรก ที่อยู่นอกลูป และนั่นคือบั๊ก
      อย่างไรก็ตาม ไม่เห็นโค้ดที่ตรวจยืนยันขั้นสุดท้ายว่าขนาดที่ดาวน์โหลดมาเท่ากับ *buf_size หรือก็คือ Content-Length
      ถ้าเงื่อนไขนี้ไม่เป็นจริง ก็อาจเป็นสัญญาณว่าการเชื่อมต่อถูกปิดเร็วเกินไป
  • นี่เป็นบั๊กชัดเจนและก็ดีแล้วที่แก้ไข แต่ก็อดสงสัยไม่ได้ว่าใครจะบูตเครื่องของตัวเองจากโฮสต์ที่ไม่น่าเชื่อถือ
    ถ้าผู้โจมตีควบคุมบริการ HTTP ได้มากพอจะส่งเฮดเดอร์อันตรายมาได้ การหลีกเลี่ยง overflow นี้ก็น่าจะเป็นปัญหาที่เล็กที่สุดแล้ว เพราะใบรับรองก็น่าจะถูกเจาะด้วย และยังสามารถใส่มัลแวร์ลงใน payload ที่ถูกต้องตามสเปกแล้วส่งมาได้อีก
    มันเป็นบั๊กก็จริง แต่ไม่แน่ใจว่าเป็นระดับ Critical หรือเปล่า

    • คนที่ผลักดัน Secure Boot ก็เป็นพวกแนวเดียวกัน
      พวกเขาเชื่ออย่างจริงจังในยุทธศาสตร์ความปลอดภัยที่กำหนดว่าทุกอย่างที่อาจถูกเจาะได้ต้องไม่สามารถทำงานได้ถ้าไม่ได้ลงนามด้วย Secure Boot
      ถ้ามีของที่ถูกลงนามแต่มีช่องโหว่อยู่แม้เพียงชิ้นเดียว ก็อาจใช้มันเพื่อปลดล็อกดิสก์เข้ารหัสด้วย Secure Boot+TPM ของทุกคนได้
      ยากจะเข้าใจว่าทำไมแนวทางนี้ถึงถูกมองว่าเป็นโมเดลความปลอดภัยที่ใช้ได้ และช่องโหว่แบบนี้ก็มีอยู่เต็มไปหมดแล้ว
      แถมยังมองข้ามช้างตัวโตในห้องอย่าง Windows ไปโดยสิ้นเชิง
      ตัวอย่างของวิธีคิดแบบนี้: https://lkml.org/lkml/2018/4/3/767
      แม้จะมีความกังวลจาก Linus แต่หลายดิสโทรก็เปิด integrity mode จริงเมื่อบูตด้วย Secure Boot
      น่าจะเป็นเพราะนโยบายของ Microsoft และสถานการณ์ที่ดิสโทรซึ่งต้องการลายเซ็น UEFI ของ Microsoft ถูกบังคับให้ทำตามขั้นตอนที่อธิบายไว้ในเธรดนั้น
      ผลก็คือเมื่อเปิด Secure Boot ฟีเจอร์ของดิสโทรมักถูกจำกัด เช่น ใช้โหมดจำศีลไม่ได้
    • การป้องกันที่ดีมีได้ด้วยการวาง การป้องกันหลายชั้น เท่านั้น และบั๊กนี้ก็เจาะรูหนึ่งชั้นในนั้น
    • มันอาจถูกใช้เพื่อเจาะเข้าอุปกรณ์ที่ถูกล็อกบางประเภทได้
    • ถ้าดูคำอธิบายที่ยอดเยี่ยมในอีกเธรดหนึ่ง https://news.ycombinator.com/item?id=39135275 ก็อาจมองว่าเป็น Critical ได้ เพราะเวกเตอร์การโจมตีไม่ได้จำกัดอยู่แค่ HTTP
  • เวลามีเรื่องสำคัญเกิดขึ้นควรใช้ HTTP ที่มี S
    การบูตอุปกรณ์ก็เข้าข่ายนี้ และ เฮดเดอร์ HTTPS ก็ถูกเข้ารหัสอยู่แล้วเสมอ
    ถึงอย่างนั้นก็เป็นบั๊กที่หาพบได้ดี

    • HTTPS ไม่เกี่ยวกับประเด็นนี้
      เฮดเดอร์ที่ไม่ถูกต้องส่งมาได้ทั้งสองแบบ
    • ไม่แน่ใจว่า HTTPS ใช้ได้กับกรณีนี้หรือไม่
      เพราะการเข้ารหัสต้องอาศัยเวลาและวันที่ที่ถูกต้อง
      RTC อาจใช้ได้ แต่ก็ไม่รู้ว่าจัดการเขตเวลาได้ดีหรือเปล่า และอย่างไรก็ยังอาจตั้งเวลาคลาดเคลื่อนได้
    • ปัญหานี้ไม่เกี่ยวเลยว่าจะมี S หรือไม่มี S
      ดูเหมือนจะยังเข้าใจปัญหาไม่ถูกต้อง
  • Content-length ไม่ใช่ความยาวของเนื้อความจริง แต่เป็น ความยาวหลัง Content-encoding

    • “HTTP/1.1 เป็นโปรโตคอลที่เรียบง่ายอย่างน่ารื่นรมย์ ถ้าคุณเมินรายละเอียดส่วนใหญ่ไป”
  • ใน build ของ shim เหล่านี้มี httpboot รวมมาด้วยหรือ?
    เท่าที่รู้ shim มีไว้แค่รัน EFI binary อื่นจากดิสก์ และไม่แน่ใจว่าเคยเห็นฟังก์ชันบูตผ่านเครือข่ายของ shim ถูกใช้งานจริงหรือเปล่า

  • อาจเป็นเพราะผมไม่รู้เอง แต่คิดว่า HTTP client ส่วนใหญ่จะอ่านแค่ตาม Content-Length ที่กำหนด และถ้าอ่านได้ไบต์น้อยกว่าค่า Content-Length ก็จะถือว่าเป็นข้อผิดพลาด

    • HTTP client นั้น UEFI จัดให้มาในรูปของ EFI driver
      เท่าที่ดู สเปก UEFI เหมือนจะไม่ได้ระบุพฤติกรรมอย่างชัดเจนเมื่อเฮดเดอร์ Content-Length ไม่ตรงกับความยาวของ response body
      เพราะงั้นก็มีความเป็นไปได้มากที่บาง implementation จะสร้างคำขอ connection:close แล้วไม่ตรวจ Content-Length เลย
      ช่องโหว่นี้ถูกรายงานโดย MSRC และคำอธิบาย CVE ก็ไม่ได้กล่าวถึงการนำไปใช้โจมตีจริง
      อาจมีการเปิดเผยภายหลัง หรืออาจเป็นเพียงปัญหาในทางทฤษฎี
  • ช่วยอธิบายได้ไหมว่าทำไมการอ่านน้อยกว่าความยาวเนื้อความจริงถึงอันตราย?
    ผมกลับคาดว่ากรณีตรงข้ามน่าจะอันตรายกว่า

    • เมื่อ shim ดึงไฟล์ผ่าน HTTP หรือโปรโตคอลที่เกี่ยวข้อง มันต้องจัดสรรบัฟเฟอร์ไว้เก็บข้อมูลที่รับมา
      แต่กลับเอาขนาดมาจากเฮดเดอร์ HTTP ที่ผู้โจมตีควบคุมได้ และผู้โจมตีก็กำหนดขนาดให้เล็กกว่าข้อมูลที่ส่งมาจริงได้
      ในกรณีนี้ โค้ดใช้ค่าในเฮดเดอร์ตอนจัดสรร แต่ตอนคัดลอกจากบัฟเฟอร์รับกลับใช้ขนาดจาก metadata ของโปรโตคอล จึงเกิด การเขียนนอกขอบเขต
    • ตามคำอธิบายคือจัดสรรบัฟเฟอร์ตาม Content-Length แต่คัดลอกตามขนาดของบัฟเฟอร์ที่รับมาจริง ทำให้เขียนออกนอกขอบเขตที่จัดสรรไว้