1 điểm bởi GN⁺ 2025-03-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • Backdoor xz được phát hiện vào tháng 3/2024 có thể dẫn đến thực thi mã từ xa trong các môi trường sshd bị ảnh hưởng, và trường hợp NixOS cho thấy các bản dựng tái lập có thể phơi bày một cách cơ học việc can thiệp vào chuỗi cung ứng
  • Thành phần độc hại không ẩn trong toàn bộ kho Git của xz, mà trong tarball phát hành do maintainer cung cấp của các phiên bản 5.6.05.6.1; trong quá trình build, nó tạo ra shell script và object file từ các tệp kiểm thử .xz giả
  • Cuộc tấn công lợi dụng ifunc của glibc để thực thi mã khi liblzma được nạp, nhắm vào các môi trường họ Debian/Fedora nơi sshd phụ thuộc bắc cầu vào liblzma thông qua libsystemd
  • NixOS thường ưu tiên các kho lưu trữ mã nguồn do GitHub tự động tạo, nhưng vì xz nằm ở giai đoạn bootstrap của nixpkgs, để tránh vòng lặp phụ thuộc autoconf thì phải tin vào tarball của maintainer
  • Cách phòng vệ được đề xuất là sau bootstrap, build lại xz từ mã nguồn GitHub rồi so sánh với sản phẩm tạo ra từ tarball hiện có; khi bật điều kiện, sự khác biệt về kích thước liblzma.so và việc thêm symbol _get_cpuid đã lộ ra

Backdoor xz đã ẩn mình như thế nào

  • xz là phần mềm nén/giải nén thường được dùng khi giải nén source tarball trên các đường dẫn cốt lõi của các bản phân phối Linux
  • Vào tháng 3/2024, một backdoor được phát hiện trong xz; maintainer độc hại Jia Tan đã xây dựng niềm tin trong khoảng 3 năm, giành được quyền push vào kho, rồi dần dần đưa mã bị làm rối vào giữa các đóng góp hợp pháp
  • Andres Freund khi điều tra hiện tượng ssh chậm hơn 500ms trên nhiều máy Debian unstable đã lần theo đến liblzma, xác định và ghi tài liệu về backdoor
  • Mục tiêu của backdoor là chiếm quyền điều khiển ssh, cho phép kẻ tấn công thực thi lệnh tùy ý trên máy nạn nhân khi đăng nhập bằng một khóa RSA cụ thể

Tarball độc hại và object được tạo trong quá trình build

  • Các thành phần độc hại không được đưa trực tiếp vào toàn bộ mã của kho Git xz, mà nằm trong các tarball phát hành xz 5.6.05.6.1 do Jia Tan build, ký và phân phối
  • Một số tệp .xz dùng cho kiểm thử trong kho Git có giấu mã máy, và trong tarball phát hành có thêm các thay đổi ngụy trang để trích xuất chúng
  • Đặc biệt, thay đổi trong m4/build-to-host.m4 trông bề ngoài vô hại và có cả chú thích, nhưng thực chất che giấu một chuỗi lệnh giải mã và gỡ rối nhiều tệp kiểm thử .xz giả
  • Kết quả của quá trình này gồm hai thứ
    • Shell script được thực thi trong quá trình build xz
    • Binary object file độc hại
  • Shell script chạy trong quá trình build đảm nhận hai vai trò
    • Kiểm tra các điều kiện để backdoor thực thi, như bản phân phối Linux cụ thể, tính năng của glibc, việc có cài ssh hay không
    • Sửa object hợp lệ liblzma_la-crc64_fast.o để nó sử dụng symbol _get_cpuid của object file backdoor
  • Bài xz script analysis của Russ Cox trình bày chi tiết quá trình tạo ra tài nguyên độc hại trong lúc build

Chiếm quyền ifunc dẫn tới sshd

  • Với chương trình liên kết động, tại thời điểm chạy, dynamic loader nạp các shared library vào bộ nhớ và điền địa chỉ symbol vào Global Offset Table (GOT)
  • ifunc của glibc là tính năng chọn một trong nhiều hiện thực của cùng một hàm tại thời điểm nạp động, và backdoor xz dùng nó làm đường thực thi mã độc
  • ssh của một số bản phân phối như Debian và Fedora được liên kết với libsystemd để hỗ trợ thông báo của systemd, còn libsystemd lại liên kết với liblzma
    • Trong môi trường này, sshd phụ thuộc bắc cầu vào liblzma
  • Khi sshd chạy, dynamic loader nạp libsystemdliblzma; nếu backdoor đã được cài, mã độc sẽ được thực thi trong lúc nạp liblzma
  • Backdoor khiến ifunc resolver gọi symbol _get_cpuid độc hại, rồi thao túng GOT khi nó chưa ở chế độ chỉ đọc để đổi địa chỉ RSA_public_decrypt sang hàm độc hại
  • Có thể xem phân tích chi tiết của Securelist tại đâybài nghiên cứu tổng hợp vector tấn công cùng biện pháp giảm thiểu làm tài liệu tham khảo

Nguồn đáng tin cậy và ràng buộc bootstrap của NixOS

  • Cuộc tấn công này có thể phát huy hiệu quả vì nhiều bản phân phối build xz từ tarball do maintainer cung cấp thay vì mã nguồn gốc trên Git forge
  • Quy trình tarball có các lý do lịch sử và thực tiễn
    • Đây là cách làm của các bản phân phối Linux thời kỳ đầu, có trước git
    • Lưu giữ trạng thái mã nguồn tại thời điểm phát hành dưới dạng một archive tự chứa
    • Có thể giảm gánh nặng build bằng cách bao gồm các sản phẩm trung gian như manpage, configure script
    • Có thể đạt hiệu quả lưu trữ nhờ nén
  • Từ góc độ bảo mật, khi có thể về mặt kỹ thuật, nên build từ nguồn do chủ thể đáng tin cậy nhất xác thực
    • Nếu dự án được phát triển trên GitHub, có thể dùng archive do GitHub tự động tạo cho mỗi bản phát hành
    • Lập luận tương tự cũng có thể áp dụng cho các nền tảng bên thứ ba đáng tin cậy như Codeberg, SourceHut, GitLab
  • NixOS sử dụng mô hình quản lý gói theo kiểu hàm, trong đó các gói định nghĩa công thức build bằng biểu thức Nix
  • Các maintainer NixOS có văn hóa dùng archive mã nguồn do GitHub tự động tạo thông qua fetchFromGitHub khi có thể, nhưng gói xz lại dùng fetchurl để lấy tarball maintainer được tải lên thủ công
  • Lý do là xz nằm trong giai đoạn bootstrap của nixpkgs
    • bootstrap là quá trình cho phép build lại toàn bộ các gói nixpkgs từ một tập seed binary nhỏ
    • stdenv là môi trường biên dịch cơ bản được các gói khác sử dụng
    • stdenv phụ thuộc vào xz lúc runtime, và các gói như coreutils cần xz để giải nén các source archive .tar.xz
  • Để build xz từ mã nguồn GitHub cần autoconf nhằm tạo configure script, nhưng autoconf cũng phụ thuộc vào xz
    • Tarball do maintainer cung cấp đã có sẵn configure script được tạo, nên có thể cắt vòng lặp phụ thuộc này
    • Vì vậy, tại điểm xz được build trong đồ thị nixpkgs, khó dùng source archive từ GitHub và phải tin vào tarball của maintainer

So sánh hội tụ sản phẩm thay vì so sánh mã nguồn

  • Cách so sánh tarball của maintainer với source tarball GitHub có giống nhau không nghe có vẻ tự nhiên, nhưng trong thực tế lại không phù hợp
  • Daniel Stenberg giải thích rằng việc tarball phát hành khác với mã nguồn là một tính năng
    • Có thể đưa vào tarball các sản phẩm trung gian như manpage, configure script
    • Điều này đặc biệt hữu ích khi bản phân phối muốn tránh phụ thuộc vào autoconf
  • Từ góc độ bảo mật chuỗi cung ứng, sự linh hoạt này biến thành gánh nặng phải tin rằng maintainer trung thực
  • Bản dựng tái lập là thuộc tính mà khi build hai lần trong cùng điều kiện, sản phẩm đầu ra giống hệt nhau đến từng bit
  • Nhóm reproducible-builds đặt mục tiêu làm cho nhiều gói nhất có thể tái lập được, và bài Reproducible Builds: Increasing the Integrity of Software Supply Chains xem đây là thuộc tính giúp tăng độ tin cậy khi phân phối sản phẩm binary
  • Cách tiếp cận làm cho chính việc tạo tarball có thể tái lập đã được dự án PostgreSQL áp dụng
    • Người dùng có thể độc lập xác minh rằng tarball của maintainer được tạo một cách trung thực từ mã nguồn gốc
    • Tuy nhiên, vì upstream maintainer phải triển khai nên tốc độ áp dụng trong cộng đồng FOSS có thể chậm

Quy trình phát hiện được đề xuất trên NixOS

  • Nếu xz tái lập được đến từng bit và tarball của maintainer không chứa thay đổi ảnh hưởng đến quá trình build, thì build từ tarball GitHub và build từ tarball maintainer phải tạo ra cùng sản phẩm
  • Phương pháp được đề xuất là build thêm xz một lần sau bootstrap; lần này dùng source archive GitHub thông qua fetchFromGitHub và so sánh với sản phẩm xz từ giai đoạn bootstrap hiện có
  • Ví dụ triển khai là đặt after-bootstrap.nix dưới pkgs/tools/compression/xz và build xz 5.6.1 từ mã nguồn GitHub
    • Tarball maintainer đã có sẵn configure script, nhưng khi build từ mã nguồn GitHub phải dùng autoconf, libtool, automake... để chạy ./autogen.sh
  • Thêm compareArtifacts làm bước so sánh để so sánh liblzma.so ở cuối quá trình build
    • Ví dụ chỉ so sánh liblzma.so, là tệp bị backdoor sửa đổi, nhưng có thể tổng quát hóa cho tất cả sản phẩm binary và library
  • Ở revision master bình thường, build xz-after-bootstrap thành công và hai sản phẩm giống hệt nhau
  • Ở revision c53bbe3 có chứa backdoor, ban đầu chúng trông vẫn giống nhau
    • Trên NixOS, backdoor không được kích hoạt; một trong các lý do là script cài đặt kiểm tra việc biến RPM_ARCH có được đặt hay không
    • Khi đặt env.RPM_ARCH = true; để kích hoạt điều kiện, khác biệt trong binary liblzma.so đã lộ ra
  • Khi lưu lại và so sánh hai tệp liblzma.so, sản phẩm chứa backdoor có kích thước 258KB, còn sản phẩm build từ mã nguồn GitHub sau bootstrap là 210KB, chênh khoảng 48KB
  • Khi so sánh symbol bằng nm, các symbol _cpuid, _get_cpuid, __tls_get_addr@GLIBC_2.3 đã được thêm vào; _get_cpuid là symbol được ghi nhận trong nhiều báo cáo kỹ thuật về backdoor xz

Cách áp dụng và giới hạn

  • Biện pháp bảo vệ được đề xuất là áp dụng cho mọi gói được build ở giai đoạn bootstrap và không dùng source archive đáng tin cậy
  • Nếu thêm các gói *-after-bootstrap vào channel blocker, thất bại build có thể trở thành cảnh báo lớn cần maintainer can thiệp
  • Một pull request cho proof of concept về trường hợp xz đã được mở trong kho nixpkgs tại đây
  • Phương pháp này cần giả định rằng gói mục tiêu tái lập được đến từng bit
    • Lấy mẫu 17 revision của nixpkgs-unstable từ năm 2017 đến 2023 và build lại các non-fixed-output derivation của stdenv bằng nix-build --check
    • Ở mọi revision, xz đều tái lập được đến từng bit
    • Trong 12/17 revision, có 1–2 gói build được nhưng không tái lập được; ví dụ gcc nhất quán không tái lập được trong giai đoạn 2017–2021, còn bash không tái lập được cho đến năm 2019
  • Không thể áp dụng phương pháp này cho mọi gói stdenv, nhưng về lâu dài có thể bật có chọn lọc cho các gói có tính tái lập tốt
  • Vấn đề trusting trust của Ken Thompson vẫn là giới hạn lý thuyết của phương pháp này
    • Cần giả định rằng xz không đáng tin trong giai đoạn bootstrap không thể gián tiếp làm ô nhiễm build xz-after-bootstrap khiến sản phẩm trông giống nhau
    • Kiểu tấn công này có thể rất phức tạp, nhưng bản thân phương pháp dựa trên giả định đó
  • Cách phát hiện này chỉ phát hiện khi thay đổi trong tarball ảnh hưởng đến sản phẩm cuối cùng
    • Vì file thực thi của NixOS không chứa backdoor, nếu không kích hoạt điều kiện riêng thì trường hợp backdoor này có lẽ đã không bị phát hiện

1 bình luận

 
GN⁺ 2025-03-24
Ý kiến trên Hacker News
  • Cần phải chỉ ra rằng NixOSbuild có thể tái lập đã không phát hiện được backdoor xz. Trên thực tế, NixOS cũng đã phân phối bản build xz độc hại cho người dùng, chỉ là mã độc không hoạt động vì nó không nhắm vào NixOS mà thôi
    Một nhà phát triển NixOS cũng nói rằng “khi backdoor bị công khai, tôi đã ngạc nhiên khi biết phiên bản xz độc hại đã được phân phối tới người dùng của chúng tôi”. Như mọi khi, lý thuyết và thực tế khác nhau; thứ khiến xz xảy ra không hẳn là lỗ hổng kỹ thuật mà là lỗ hổng con người trong thế giới thực. Cộng đồng thường khó thừa nhận rằng những vấn đề như vậy không phải lúc nào cũng có thể được vá chỉ bằng phần mềm tốt hơn

    • Cấu hình khai báo của Nix khá hữu ích trong việc tăng khả năng phòng thủ trước tấn công theo nhiều cách, nhưng vẫn còn rất nhiều tiềm năng chưa được khai thác. Cá nhân tôi muốn ưu tiên triển khai container dùng một lần với độ phân tách chi tiết, và Guix đã có tính năng như vậy
      Nếu có thể chạy mọi tiến trình với quyền hạn chế, và không cho chúng truy cập cả ~/ ngoại trừ các thư mục cần thiết cho công việc, thì chẳng hạn có thể ngăn một gói pip độc hại đánh cắp khóa SSH. Dù vậy, tôi cho rằng lý do backdoor xz không hoạt động trên NixOS là do cấu trúc hệ thống tệp phi FHS đặc thù của NixOS
    • Dù sao bài viết này vẫn hay. NixOS đang đưa ra một giải pháp kỹ thuật để ngăn các artifact build tách rời khỏi mã nguồn, tức các artifact không thể tái lập, còn backdoor xz thì lại ẩn ngay trong artifact build đó
    • Đúng vậy. Tiêu đề có vẻ như muốn nói cách làm của Nix có thể đã phát hiện backdoor, nhưng thực ra bài viết giống một đề xuất về việc nên thay đổi Nix thế nào để có thể phát hiện những backdoor như vậy
    • Tôi thích việc bài viết nhấn mạnh rằng build có thể tái lập đã không thực sự bắt được backdoor xz trước các con đường phát hiện khác
      Dạo này, nếu thực tế khách quan không phù hợp với cảm xúc hay hệ giá trị cá nhân của ai đó về thực tế khách quan, nó thường bị xem nhẹ. Tôi có hệ giá trị cá nhân của mình, nhưng không cho rằng thực tế kém quan trọng hơn cảm xúc của tôi về nó. Như câu tôi hay nói, khác biệt giữa lý thuyết và thực tế là: về lý thuyết thì hai thứ giống nhau, nhưng trong thực tế thì không. Hy vọng nhận thức rằng các build có thể tái lập của NixOS lẽ ra có thể bắt được cuộc tấn công xz nhưng đã không làm được sẽ dẫn tới những cải tiến trong việc phân tích các build như vậy để phát hiện các cuộc tấn công khác sớm hơn
    • Lý do nghe hơi buồn cười. Bootstrap của NixOS tải mã nguồn xuống, mà mã nguồn đó là một tarball được nén bằng xz
  • Tôi có cảm giác tác giả bị thu hẹp tầm nhìn quá mức vào cách chuyện này tình cờ xảy ra lần này. Vụ Jia Tan chỉ là một mẫu duy nhất, nên cho rằng chỉ có cách đó mới khả thi là thiển cận
    Có thể dễ dàng tưởng tượng ra nhiều kịch bản trong đó biện pháp phòng thủ được đề xuất ở đây sẽ không hiệu quả. Là người dùng Nix, tôi cũng nghĩ khả năng NixOS bắt được chuyện này là thấp. Thực tế là nó đã không bắt được. Tôi vừa nói lần sau có thể xảy ra theo cách khác, nhưng tin vào Nix mà không có bằng chứng cũng là ngu ngốc

  • Ở đây NixOS không liên quan lắm. Backdoor xz nhắm cụ thể vào Red Hat và Debian
    Theo cùng logic đó, nói rằng backdoor xz không ảnh hưởng đến Windows cũng có mức độ liên quan tương tự. Trớ trêu là cuối cùng backdoor này lại được một nhân viên Microsoft phát hiện, điều này thường bị bỏ qua

    • Một NixOS hoặc Guix được cải thiện đôi chút đã có thể tự động bắt được backdoor này ngay khi nó được đưa vào kho. Vì vậy nó có liên quan
  • Bài viết nói rằng các distro nên lấy mã nguồn trực tiếp từ hệ thống quản lý phiên bản, chẳng hạn Github, thay vì tarball cài đặt truyền thống
    Nhưng tôi không rõ điều này giải quyết được gì. Chẳng phải một maintainer độc hại chỉ cần thêm trực tiếp một khối binary vào kho mã nguồn là xong sao? Tác giả có vẻ xem Github là đáng tin cậy như thể Github xác minh mã, nhưng dĩ nhiên Github không làm việc đó

    • Cách này giải quyết vấn đề thường gặp là “thứ đã được review” và “mã nguồn dùng để build phần mềm” không giống nhau
      Build có thể tái lập đã được xác minh có thể đã giúp ngăn các trường hợp như vụ xâm phạm xz utils hay việc SolarWinds Orion bị chỉnh sửa, và hoàn toàn đáng để làm
    • Phê bình đó hơi không đúng trọng tâm. Nếu build từ source thì bạn và mọi người có thể kiểm tra source, nhưng nếu build từ tarball được cung cấp thì quá trình autoconf thay đổi các tệp, nên về bản chất nó khác với source gốc khi so sánh
      Nếu bạn tải binary từ GitHub Releases rồi chạy, thì chỉ còn cách tin hoàn toàn vào maintainer. Nix chỉ làm vậy với các gói closed source
  • Lập luận dựa vào việc maintainer XZ đã giấu mã độc trong tarball không được check-in vào Git
    Tác giả cho thấy Nix có thể được cấu hình để tạo tarball từ Git rồi đưa vào build binary. Nhưng tôi không hiểu vì sao đây lại là tính năng cần Nix hay NixOS. Bất kỳ hệ thống build nào, kể cả những thứ đi vào RPM hay Deb, đều có thể được cấu hình để tạo tarball như một bước trung gian. Thực tế Debian từ lâu đã coi build có thể tái lập là một mục tiêu quan trọng và đã làm việc về nó. https://wiki.debian.org/ReproducibleBuilds

    • Bài viết thực ra có trích dẫn dự án reproducible-builds trong mục “Leveraging bitwise reproducibility”. Ý chính của bài có hai phần
      Thứ nhất, quy trình build của NixOS cần xz quá sớm trong giai đoạn bootstrap, nên không thể thực hiện build toàn bộ từ source của xz. Thứ hai, bài viết đề xuất điều chỉnh nixpkgs để tự động phát hiện liệu các dependency nixpkgs cần ở giai đoạn bootstrap sớm có bị xâm phạm hay không. Các hệ sinh thái khác dĩ nhiên cũng có thể thử build toàn bộ từ source để phát hiện bất一致. Điểm cốt lõi của bài là hiện tại nixpkgs chưa làm được việc đó
  • Nếu muốn tập trung vào một trường hợp NixOS lẽ ra có thể ngăn được, hãy nhìn vào sự cố CrowdStrike. Chỉ riêng việc nếu cấu hình hôm nay không chạy thì có thể boot bằng cấu hình hôm qua cũng đã giảm thiểu phần lớn thiệt hại

    • Tuy nhiên đó là vấn đề của Windows, vốn thiếu tính linh hoạt khi boot. Ubuntu cũng có thể làm như vậy trên ZFS
  • Nếu dùng một framework mà bạn tin tưởng thì sẽ an toàn cho đến khi chính framework đó bị tấn công. Backdoor xz có thể đã được phát hiện, nhưng nó không được thiết kế với mục đích hoạt động trong hệ sinh thái Nix.
    Một ngày nào đó, nếu một nhà phát triển cốt lõi của Nix là gián điệp, hoặc xảy ra tình huống tương tự, thì cũng sẽ có các cuộc tấn công nhắm vào hệ sinh thái Nix. Tôi không muốn mọi người trả lời theo kiểu Nix vốn dĩ là an toàn. Nếu trong 1–2 năm tới Nix bị tấn công thành công, tôi sẽ muốn tìm đến để buộc họ thừa nhận rằng mình đã sai

    • Tiêu chuẩn, dù trước đây hay về sau, không bao giờ là bảo mật tuyệt đối. Đó là một tiêu chuẩn bất khả thi mà không thứ gì có thể đáp ứng. Dù vậy, về mặt khách quan, phần mềm ngày nay nhìn chung an toàn hơn 30 năm trước là sự thật
      Lập luận mạnh hơn là “Nix khó tấn công hơn và tốn kém hơn so với các hệ thống build truyền thống”. Vì vậy, nếu tìm được cách tấn công Nix với chi phí rẻ, lúc đó hãy quay lại. Trước thời điểm đó, ở cấp độ kỹ thuật, nói rằng Nix khó tấn công hơn các hệ thống thay thế ít nhất là hợp lý, và trên thực tế cũng rất có khả năng đúng
    • Backdoor đó không nhắm vào Nix, nhưng để không bị lộ, nó vẫn phải không gây nghi ngờ trong quá trình được build trên Nix
  • NixOS có tiến xa hơn một chút, nhưng hầu hết các bản phân phối khác cũng biên dịch mọi thứ từ mã nguồn, xác minh bằng mật mã rằng mã nguồn được dùng không bị sửa đổi, và có phụ thuộc được chỉ định theo phiên bản giữa các gói. Debian cũng có các build có thể tái lập
    Vấn đề nằm ở chỗ hệ thống build đã không loại bỏ các object file đã được biên dịch sẵn trước khi build từ mã nguồn. Dù có sửa điều đó, nếu không ai kiểm tra mã nguồn thì vẫn có thể thêm backdoor tùy ý, và cả NixOS lẫn các bản phân phối khác đều không thể ngăn được việc đó

  • Đây là một phân tích kỹ thuật xuất sắc, nhưng tiêu đề thì sai và dễ gây hiểu nhầm. Dù có thể nói là “về mặt kỹ thuật thì đúng”, nhưng nói nhẹ nhất thì nó gần với ý nghĩa là backdoor đã được cài vào hơn
    Nó cho thấy rõ rằng cần có các công cụ quản lý build vượt ra ngoài các lớp union filesystem. Ví dụ, cần theo dõi và cưỡng chế để test không thể làm ô nhiễm các artifact build. Cần tạo một đồ thị truy vết quan hệ nhân quả về việc trong quá trình build, file nào ảnh hưởng đến file nào; biểu diễn đồ thị đó một cách tường minh, rồi cưỡng chế theo nó hoặc báo cáo khác biệt so với đồ thị truy vết trước đó

  • Đúng vậy. Việc che giấu backdoor chắc chắn sẽ khó hơn nhiều. Nhưng vẫn còn rất xa mới gọi là bất khả thi
    Nếu muốn, lúc nào cũng có thể giấu backdoor trong mã nguồn. Chỉ là sẽ tốn nhiều công sức hơn để khiến nó trông giống một lỗi có vẻ hợp lý, và khả năng bị phát hiện cũng cao hơn mà thôi