1 điểm bởi GN⁺ 2024-01-27 | 1 bình luận | Chia sẻ qua WhatsApp
  • Commit 0226b56 của rhboot/shim đã sửa CVE-2023-40547, phát sinh do tin cậy nguyên vẹn giá trị kích thước trong header HTTP trong quá trình nhận tệp
  • Nếu header bị chỉnh sửa chỉ định kích thước nhỏ hơn dữ liệu thực tế nhận được, shim có thể cấp phát vùng nhớ nhỏ hơn buffer cần thiết
  • Mã cũ dùng giá trị header để cấp phát, nhưng khi sao chép lại dùng metadata của giao thức, có thể dẫn đến ghi vượt giới hạn (out-of-bounds write)
  • Bản vá thêm kiểm tra *buf_size < rx_message.BodyLength trong receive_http_response() của httpboot.c; nếu thất bại thì xử lý bằng EFI_BAD_BUFFER_SIZEInvalid Content-Length
  • Phạm vi thay đổi là thêm 7 dòng, xóa 1 dòng trong một tệp httpboot.c, đồng thời sửa lỗi chính tả Content-Lenght thành Content-Length

Luồng phát sinh lỗ hổng

  • CVE-2023-40547 là vấn đề xảy ra khi shim lấy tệp qua HTTP hoặc các giao thức liên quan
  • Trong quá trình cấp phát buffer để lưu dữ liệu nhận được, giá trị kích thước trong header HTTP được sử dụng
  • Header HTTP có thể bị chỉnh sửa và có thể chỉ định kích thước nhỏ hơn dữ liệu thực tế nhận được
  • Luồng xử lý cũ dùng giá trị header để cấp phát buffer, còn khi sao chép dữ liệu từ buffer rx thì lại dựa trên metadata của giao thức
  • Sự khác biệt này có thể khiến dữ liệu lớn hơn buffer đã cấp phát bị sao chép vào, từ đó gây ra ghi vượt giới hạn (out-of-bounds write)

Nội dung bản vá

  • Bản vá thêm kiểm tra phòng vệ vào receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) trong httpboot.c
  • Khi *buf_size == 0, bản vá sửa lỗi chính tả trong thông báo lỗi cũ rồi chuyển đến goto error
    • Failed to get Content-LenghtFailed to get Content-Length
  • Kiểm tra mới xác nhận điều kiện *buf_size < rx_message.BodyLength
    • Nếu điều kiện đúng, đặt efi_status = EFI_BAD_BUFFER_SIZE
    • In lỗi Invalid Content-Length
    • Sau đó chuyển đến goto error

Phạm vi thay đổi

  • Tệp được thay đổi là một tệp duy nhất: httpboot.c
  • Khối lượng thay đổi là thêm 7 dòng, xóa 1 dòng
  • Điểm cốt lõi là logic kiểm tra xem độ dài nội dung nhận được rx_message.BodyLength có lớn hơn kích thước cấp phát *buf_size hay không

Ghi nhận liên quan

  • Commit này được đánh dấu là thay đổi nhằm khắc phục CVE-2023-40547
  • Thông điệp commit nêu rõ vấn đề bắt nguồn từ việc tin cậy sai vào header HTTP
  • Người báo cáo lỗ hổng được ghi nhận là Bill Demirkapi thuộc Microsoft Security Response Center

1 bình luận

 
GN⁺ 2024-01-27
Các ý kiến trên Hacker News
  • shim là một EFI bootloader thường được dùng trong các bản phân phối Linux muốn bật Secure Boot
    Từ góc nhìn của bản phân phối, họ muốn người dùng có thể bật Secure Boot dễ dàng bằng khóa ký Microsoft được cài sẵn, thay vì phải tự đăng ký khóa
    Tuy nhiên, vì Microsoft thường không ký cho các bootloader GPL như GRUB, nên shim, thứ có thể được ký bằng khóa Microsoft, đã được tạo ra; shim kiểm tra chữ ký của mục tiêu mà nó sẽ khởi động bằng một khóa riêng gọi là Machine Owner Key, tức MOK
    Khi chỉ định EFI binary để shim khởi động, có thể đưa vào HTTP URL; nếu HTTP server lúc này độc hại, nó có thể gây ghi ngoài phạm vi
    Tuy vậy, thông thường shim được dùng để khởi động bootloader giai đoạn 2 cục bộ như GRUB, nên trong phần lớn cài đặt, khả năng đây trở thành vấn đề có vẻ thấp
    Secure Boot ngay từ đầu đã được thiết kế để có thể thu hồi cả các binary đã được ký thông qua danh sách DBX; khi đưa danh sách này vào UEFI, binary đó sẽ bị từ chối ngay cả khi có chữ ký hợp lệ
    Nếu chữ ký của các binary shim cũ có lỗi này được thêm vào danh sách, mỗi người có thể cập nhật danh sách trên thiết bị của mình; nó cũng có thể được phân phối bằng cập nhật capsule như LVFS, và nếu bạn tự quản lý khóa Secure Boot cùng các biến, bạn cũng có thể tải danh sách từ https://uefi.org/revocationlistfile rồi đăng ký

    • Tôi là người phát hiện lỗi trong bài gốc, và việc cho rằng vấn đề này chỉ có thể bị khai thác khi dùng HTTP boot là một hiểu lầm phổ biến
      Nếu đúng như vậy thì nó đã không được xếp mức Critical
      Lỗi này có thể bị khai thác khi mã độc có đặc quyền cục bộ ghi đè phân vùng EFI, khi thực hiện tấn công trung gian trong mạng lân cận có bật PXE boot, hoặc qua tấn công trung gian từ xa khi dùng HTTP boot
      Một kẻ tấn công từ xa không có đặc quyền, nếu ở vị trí trung gian và máy nạn nhân dùng HTTP boot, có thể khai thác mà không cần truy cập trực tiếp
      Một kẻ tấn công từ xa đã có quyền và khả năng thực thi mã trên máy nạn nhân có thể vượt qua Secure Boot nếu firmware hỗ trợ HTTP, ngay cả khi nạn nhân không dùng HTTP boot
      Ví dụ, kẻ tấn công có thể thay đổi biến thứ tự khởi động để trỏ đến server do mình kiểm soát, hoặc ghi đè bootloader trên phân vùng EFI bằng các image shim và GRUB2 hợp lệ, rồi trong grub.cfg cho chainload một shim mới qua HTTP
      Lý do là cú pháp thiết bị của GRUB2 có thể chỉ định các thiết bị được hỗ trợ, bao gồm HTTP
      Ngoài ra, một kẻ tấn công lân cận không có đặc quyền nhưng ở vị trí trung gian, nếu máy nạn nhân dùng PXE boot, có thể khai thác bằng cách nối chuỗi theo kiểu shim qua PXE → GRUB2 qua PXE → shim qua HTTP
    • Vì nhóm pháp lý của Microsoft cho rằng nếu Microsoft ký cho GRUB, một bootloader dùng giấy phép GPLv3, thì do GPLv3, các nhà phát triển có thể có quyền buộc Microsoft cung cấp khóa ký
      Nguồn: https://techcommunity.microsoft.com/t5/hardware-dev-center/u...
    • Tôi từng thắc mắc vì sao bootloader lại giao tiếp qua mạng, nhưng lời giải thích rằng EFI binary có thể được chỉ định bằng HTTP URL đã giúp tôi hiểu
    • Tôi thắc mắc shim tránh vấn đề mà Microsoft lo ngại bằng cách nào
      Nếu điều khoản chống Tivoization của GPLv3 có thể yêu cầu cung cấp khóa ký Secure Boot, thì chẳng phải khóa ký MOK cũng phải được cung cấp khi có yêu cầu sao
      Nếu vậy, tức là bất kỳ ai cũng có thể nhận được khóa để ký mã tùy ý sẽ được khởi động gián tiếp thông qua Secure Boot; tôi không rõ điều đó khác biệt đáng kể thế nào so với việc Microsoft chỉ cấp khóa ký cho một dự án GPLv3 như GRUB
    • Nếu mục đích là khởi động một bootloader giai đoạn 2 cục bộ, tôi nghĩ Windows Boot Manager cũng có thể đóng vai trò tương tự
      Trên các máy BIOS cũ, tôi từng cấu hình để WBM chainload GRUB, nhưng trên máy UEFI thì chưa thử nên không biết có điểm nào bị chặn hay không
  • Có thể có người thắc mắc “tại sao lại khởi động từ một server không đáng tin cậy hoặc đã bị xâm nhập?”, hoặc “nếu server đã bị xâm nhập thì chỉ cần gửi binary độc hại là xong, vậy chuyện này chẳng phải vô nghĩa sao?”; nói ngắn gọn thì binary cuối cùng mà shim khởi động phải được ký bằng MOK
    Vì vậy, dù khởi động trong một mạng đã bị xâm nhập, khởi động qua HTTP, hay khởi động từ một server đã bị xâm nhập, thì bất kể có HTTPS hay không, cùng mức bảo đảm bảo mật vẫn phải được duy trì
    Secure Boot không ngăn hạ cấp, nên việc một server đã bị xâm nhập có thể được dùng cho tấn công hạ cấp là vấn đề tách biệt với lỗ hổng này
    Dù sao thì chống tấn công hạ cấp cũng phải được triển khai riêng bằng một cách chắc chắn hơn
    Tuy nhiên, tôi không rõ vì sao shim cần trực tiếp hỗ trợ HTTP boot
    Lẽ ra có thể xử lý trong một EFI binary cục bộ thứ hai được ký bằng MOK, nhưng có lẽ họ đã xem đây là một tính năng tương đối đơn giản để triển khai

  • Tôi không hiểu vì sao đoạn mã này lại xử lý độ dài phần thân theo hai tiêu chí
    Theo RFC, trong HTTP/1.1, Content-Length là thông tin có thẩm quyền về độ dài phần thân của yêu cầu/phản hồi HTTP
    Dữ liệu trên đường truyền vượt quá độ dài đó, theo định nghĩa, là một phần của thông điệp khác
    Ngược lại, nếu Content-Length lớn hơn rx_message.BodyLength thì có nghĩa là chưa nhận đủ toàn bộ thông điệp, nên phải chờ thêm hoặc báo lỗi hết thời gian chờ
    Dù thế nào, nếu không có bảo đảm rằng rx_message.BodyLength bằng Content-Length thì đó là giá trị sai
    Nếu muốn xử lý khoan dung hơn thì không có lý do gì phải xem header Content-Length; chỉ cần lấy rx_message.BodyLength làm kích thước bộ đệm và diễn giải toàn bộ dữ liệu trên đường truyền là thông điệp đã nhận
    Mã hiện tại phức tạp không cần thiết, và bug kiểu này lọt vào là vì vậy

    • Nếu chỉ nhìn riêng commit đó thì rất dễ hiểu nhầm
      Nhìn vào đoạn mã xung quanh https://github.com/rhboot/shim/blob/0226b56513b2b8bd5fd281bc... sẽ thấy trong vòng lặp, nó nhận từng mảnh dữ liệu và mỗi lần đều kiểm tra dữ liệu mới có vượt quá dung lượng bộ đệm được xác định bởi Content-Length hay không
      Nhưng trước đây nó không thực hiện kiểm tra đó với lần đọc đầu tiên nằm ngoài vòng lặp, và đó chính là bug
      Tuy nhiên tôi không thấy đoạn mã kiểm tra cuối cùng xem kích thước đã tải xuống có bằng *buf_size, tức Content-Length, hay không
      Nếu điều kiện này bị phá vỡ thì có thể là dấu hiệu kết nối đã bị đóng quá sớm
  • Đây rõ ràng là bug và thật tốt là đã được sửa, nhưng tôi tự hỏi ai lại khởi động thiết bị của mình từ một host không đáng tin cậy
    Nếu kẻ tấn công đã chiếm được dịch vụ HTTP đến mức có thể gửi header độc hại, thì việc tránh lỗi tràn này là vấn đề nhỏ nhất; chứng chỉ cũng đã bị xâm phạm, và chúng cũng có thể gửi payload hợp lệ theo chuẩn nhưng chứa mã độc
    Đúng là bug, nhưng tôi không chắc nó có phải Critical hay không

    • Những người thúc đẩy Secure Boot cũng thuộc kiểu tương tự
      Họ thực sự tin vào một chiến lược bảo mật trong đó mọi thứ có khả năng bị xâm phạm đều không được ký bởi Secure Boot
      Chỉ cần có một thứ đã ký nhưng có lỗ hổng, nó có thể được dùng để giải mã đĩa mã hóa Secure Boot+TPM của tất cả mọi người
      Thật khó hiểu vì sao cách tiếp cận này từng được xem là một mô hình bảo mật hợp lệ, và các lỗ hổng kiểu này thì đã có đầy rẫy
      Hơn nữa, họ hoàn toàn phớt lờ con voi trong phòng là Windows
      Ví dụ về kiểu tư duy này: https://lkml.org/lkml/2018/4/3/767
      Bất chấp lo ngại của Linus, trong nhiều bản phân phối, nếu khởi động bằng Secure Boot thì chế độ toàn vẹn thực sự được bật
      Có khả năng là do chính sách của Microsoft và việc các bản phân phối bị buộc phải làm theo quy trình được mô tả trong thread đó để nhận chữ ký Microsoft UEFI
      Kết quả là khi bật Secure Boot, các tính năng của bản phân phối thường bị hạn chế, chẳng hạn như không dùng được chế độ ngủ đông
    • Phòng thủ tốt chỉ có thể là cách xây nhiều lớp phòng thủ như phòng thủ theo chiều sâu, và bug này tạo ra một lỗ thủng ở một trong các lớp đó
    • Nó cũng có thể được dùng để xâm nhập một số thiết bị bị khóa
    • Xem phần giải thích rất hay trong thread khác https://news.ycombinator.com/item?id=39135275, vì vector tấn công không chỉ giới hạn ở HTTP nên có thể xem là Critical
  • Khi có việc quan trọng, nên dùng chữ S trong HTTP
    Khởi động thiết bị cũng nằm trong số đó, và header HTTPS thì luôn được mã hóa
    Dù vậy, đây vẫn là một bug được tìm ra rất tốt

    • Ở đây HTTPS không liên quan
      Header sai có thể được gửi theo cả hai cách
    • Tôi không chắc HTTPS có khả thi cho mục đích này không
      Vì mã hóa cần thời gian và ngày tháng chính xác
      RTC có thể hợp lệ, nhưng tôi cũng không biết nó có xử lý múi giờ tốt không, và dù sao thời gian cũng có thể bị lệch
    • Đây là vấn đề không liên quan đến việc có chữ S hay không
      Có vẻ bạn chưa hiểu đúng vấn đề
  • Content-length không phải là độ dài phần thân thực tế, mà là độ dài sau Content-encoding

    • “HTTP/1.1 là một giao thức đơn giản thú vị nếu bỏ qua phần lớn mọi thứ”
  • Các bản build shim này có bao gồm httpboot không?
    Theo tôi biết, shim chỉ là để chạy các EFI binary khác trên đĩa, và tôi không nhớ từng thấy tính năng khởi động qua mạng của shim thực sự được dùng

  • Có thể tôi không hiểu rõ, nhưng tôi tưởng hầu hết HTTP client chỉ đọc đến đúng Content-Length đã chỉ định, và coi là lỗi nếu số byte đọc được ít hơn Content-Length

    • HTTP client được UEFI cung cấp dưới dạng EFI driver
      Theo tôi thấy, đặc tả UEFI dường như không quy định cụ thể hành vi khi header Content-Length và độ dài phần thân phản hồi không khớp nhau
      Vì vậy hoàn toàn có khả năng một số triển khai chỉ tạo yêu cầu connection:close và không kiểm tra Content-Length
      Lỗ hổng này do MSRC báo cáo và mô tả CVE không có nội dung về khai thác thực tế
      Có thể sau này sẽ được công bố, hoặc cũng có thể chỉ là vấn đề mang tính lý thuyết
  • Có thể giải thích vì sao đọc ít hơn độ dài phần thân thực tế lại nguy hiểm không?
    Tôi đã nghĩ điều ngược lại mới nguy hiểm

    • Khi lấy tệp qua HTTP hoặc giao thức liên quan, shim cố gắng cấp phát bộ đệm để lưu dữ liệu nhận được
      Nhưng kích thước lại được lấy từ header HTTP có thể bị thao túng, và kẻ tấn công có thể chỉ định một kích thước nhỏ hơn dữ liệu nhận được
      Trong trường hợp này, mã dùng giá trị header để cấp phát, còn khi sao chép từ bộ đệm nhận thì dùng kích thước trong metadata của giao thức, dẫn đến ghi ngoài phạm vi
    • Theo phần mô tả, nó cấp phát bộ đệm dựa trên Content-Length, nhưng lại sao chép theo kích thước của bộ đệm thực sự nhận được, khiến việc ghi vượt ra ngoài vùng đã cấp phát