- 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ườngsshdbị ả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ản5.6.0và5.6.1; trong quá trình build, nó tạo ra shell script và object file từ các tệp kiểm thử.xzgiả - Cuộc tấn công lợi dụng ifunc của
glibcđể thực thi mã khiliblzmađược nạp, nhắm vào các môi trường họ Debian/Fedora nơisshdphụ thuộc bắc cầu vàoliblzmathông qualibsystemd - NixOS thường ưu tiên các kho lưu trữ mã nguồn do GitHub tự động tạo, nhưng vì
xznằm ở giai đoạn bootstrap củanixpkgs, để tránh vòng lặp phụ thuộcautoconfthì phải tin vào tarball của maintainer - Cách phòng vệ được đề xuất là sau bootstrap, build lại
xztừ 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ướcliblzma.sovà việc thêm symbol_get_cpuidđã lộ ra
Backdoor xz đã ẩn mình như thế nào
xzlà 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
sshchậm hơn 500ms trên nhiều máy Debian unstable đã lần theo đếnliblzma, 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ànhxz5.6.0 và 5.6.1 do Jia Tan build, ký và phân phối - Một số tệp
.xzdù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.m4trô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ử.xzgiả - 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 được thực thi trong quá trình build
- 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àisshhay không - Sửa object hợp lệ
liblzma_la-crc64_fast.ođể nó sử dụng symbol_get_cpuidcủa object file backdoor
- 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
- Bài
xzscript 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
glibclà 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à backdoorxzdùng nó làm đường thực thi mã độc sshcủa một số bản phân phối như Debian và Fedora được liên kết vớilibsystemdđể hỗ trợ thông báo củasystemd, cònlibsystemdlại liên kết vớiliblzma- Trong môi trường này,
sshdphụ thuộc bắc cầu vàoliblzma
- Trong môi trường này,
- Khi
sshdchạy, dynamic loader nạplibsystemdvàliblzma; nếu backdoor đã được cài, mã độc sẽ được thực thi trong lúc nạpliblzma - 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_decryptsang hàm độc hại - Có thể xem phân tích chi tiết của Securelist tại đây và bà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
xztừ 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
- Đây là cách làm của các bản phân phối Linux thời kỳ đầu, có trước
- 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
fetchFromGitHubkhi có thể, nhưng góixzlại dùngfetchurlđể lấy tarball maintainer được tải lên thủ công - Lý do là
xznằm trong giai đoạn bootstrap củanixpkgs- bootstrap là quá trình cho phép build lại toàn bộ các gói
nixpkgstừ một tập seed binary nhỏ stdenvlà môi trường biên dịch cơ bản được các gói khác sử dụngstdenvphụ thuộc vàoxzlúc runtime, và các gói nhưcoreutilscầnxzđể giải nén các source archive.tar.xz
- bootstrap là quá trình cho phép build lại toàn bộ các gói
- Để build
xztừ mã nguồn GitHub cầnautoconfnhằm tạo configure script, nhưngautoconfcũng phụ thuộc vàoxz- 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
xztá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
xzmột lần sau bootstrap; lần này dùng source archive GitHub thông quafetchFromGitHubvà so sánh với sản phẩmxztừ giai đoạn bootstrap hiện có - Ví dụ triển khai là đặt
after-bootstrap.nixdướipkgs/tools/compression/xzvà buildxz5.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
- Tarball maintainer đã có sẵn configure script, nhưng khi build từ mã nguồn GitHub phải dùng
- Thêm
compareArtifactslàm bước so sánh để so sánhliblzma.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
- Ví dụ chỉ so sánh
- Ở revision master bình thường, build
xz-after-bootstrapthành công và hai sản phẩm giống hệt nhau - Ở revision
c53bbe3có 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_ARCHcó được đặt hay không - Khi đặt
env.RPM_ARCH = true;để kích hoạt điều kiện, khác biệt trong binaryliblzma.sođã lộ ra
- 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
- 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_cpuidlà symbol được ghi nhận trong nhiều báo cáo kỹ thuật về backdoorxz
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-bootstrapvà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 khonixpkgstạ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-unstabletừ năm 2017 đến 2023 và build lại các non-fixed-output derivation củastdenvbằngnix-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ụ
gccnhất quán không tái lập được trong giai đoạn 2017–2021, cònbashkhông tái lập được cho đến năm 2019
- Lấy mẫu 17 revision của
- 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
xzkhông đáng tin trong giai đoạn bootstrap không thể gián tiếp làm ô nhiễm buildxz-after-bootstrapkhiế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ần giả định rằng
- 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
Ý kiến trên Hacker News
Cần phải chỉ ra rằng NixOS và build 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
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óipipđộ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 NixOSDạ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
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
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 đó
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
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
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
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
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
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