- Gói npm độc hại Shai-Hulud 2.0 đã lây nhiễm máy của lập trình viên và chiếm quyền truy cập tổ chức GitHub của Trigger.dev
- Việc lây nhiễm bắt đầu khi lập trình viên chạy
pnpm install, khiến script preinstall của gói độc hại được thực thi; công cụ TruffleHog được dùng để đánh cắp thông tin xác thực
- Kẻ tấn công đã clone 669 repository trong 17 giờ, sau đó trong 10 phút cố gắng force-push vào 199 nhánh và đóng 42 PR
- Các gói và hệ thống production không bị xâm hại, cuộc tấn công được phát hiện trong 4 phút và quyền truy cập tài khoản đã bị chặn
- Sau sự cố, hệ thống bảo mật được tăng cường bằng cách vô hiệu hóa script npm, nâng cấp lên pnpm 10, triển khai npm bằng OIDC, và áp dụng bảo vệ nhánh trên toàn bộ repository
Tổng quan cuộc tấn công
- Ngày 25 tháng 11 năm 2025, trong lúc debug nội bộ trên Slack, xuất hiện dấu hiệu bất thường khi nhiều repository có commit “init” dưới danh nghĩa Linus Torvalds
- Kết quả điều tra xác nhận sâu chuỗi cung ứng Shai-Hulud 2.0 đã lây nhiễm máy của lập trình viên và đánh cắp thông tin xác thực GitHub
- Báo cáo cho biết con sâu này đã lây nhiễm hơn 500 gói npm và ảnh hưởng tới hơn 25.000 repository
- Các gói npm chính thức của Trigger.dev (
@trigger.dev/*, CLI) không bị lây nhiễm
Dòng thời gian tấn công
- 04:11 UTC ngày 24 tháng 11: bắt đầu phát tán gói độc hại
- 20:27 UTC: máy của một lập trình viên tại Đức bị lây nhiễm
- 22:36 UTC: kẻ tấn công truy cập lần đầu và bắt đầu clone hàng loạt repository
- 15:27~15:37 UTC (ngày 25 tháng 11): thực hiện cuộc tấn công phá hoại trong 10 phút
- 15:32 UTC: phát hiện bất thường và chặn truy cập trong vòng 4 phút
- 22:35 UTC: hoàn tất khôi phục toàn bộ các nhánh
Quá trình lây nhiễm
- Khi lập trình viên chạy
pnpm install, script preinstall của gói độc hại được thực thi để tải xuống và chạy TruffleHog
- TruffleHog quét token GitHub, thông tin xác thực AWS, token npm, biến môi trường rồi gửi ra ngoài
- Trên máy bị lây nhiễm đã tìm thấy thư mục
.trufflehog-cache và các tệp liên quan
- Gói gây ra lây nhiễm đã bị xóa nên không thể lần vết
Hoạt động của kẻ tấn công
- Sau khi lây nhiễm, chúng tiếp tục trinh sát trong 17 giờ
- Dùng hạ tầng tại Mỹ và Ấn Độ để clone 669 repository
- Theo dõi hoạt động của lập trình viên và duy trì truy cập bằng token GitHub
- Tạo một repository tên “Sha1-Hulud: The Second Coming”, được cho là dùng để lưu thông tin xác thực
- Sau đó chúng thực hiện hành vi phá hoại trong 10 phút
- Cố gắng force-push 199 nhánh trong 16 repository
- Đóng 42 PR, một phần bị chặn bởi thiết lập bảo vệ nhánh
- Tất cả commit đều hiển thị dưới dạng “Linus Torvalds <email> / init”
Phát hiện và ứng phó
- Dấu hiệu bất thường được phát hiện theo thời gian thực qua cảnh báo Slack
- Trong vòng 4 phút, quyền truy cập GitHub của tài khoản bị lây nhiễm đã bị chặn; sau đó mọi quyền truy cập vào AWS, Vercel, Cloudflare và các dịch vụ khác cũng bị thu hồi
- Phân tích log AWS CloudTrail cho thấy chỉ có các lệnh gọi API chỉ đọc, không có truy cập dữ liệu production
- AWS cũng phát hiện riêng hành vi đáng ngờ liên quan đến Shai-Hulud và gửi cảnh báo
Thiệt hại và khôi phục
- 669 repository bị clone, 199 nhánh bị cố gắng force-push, 42 PR bị đóng
- Do GitHub không có reflog phía máy chủ nên việc khôi phục gặp khó khăn, nhưng toàn bộ đã được phục hồi trong 7 giờ nhờ Event API và reflog cục bộ
- Các gói npm và hạ tầng production không bị xâm hại
Lộ khóa GitHub App
- Trong quá trình điều tra, một khóa riêng GitHub App đã được tìm thấy trong thùng rác trên laptop của lập trình viên
- Khóa này có quyền read/write với repository của khách hàng và đã được xoay vòng ngay lập tức
- Cơ sở dữ liệu (nơi lưu installation ID) không bị xâm hại nên không có bằng chứng truy cập repository khách hàng, nhưng cũng không thể loại trừ hoàn toàn
- Nhóm đã yêu cầu GitHub hỗ trợ cung cấp thêm log và gửi email thông báo cho khách hàng
Phân tích kỹ thuật Shai-Hulud
- Khi
setup_bun.js chạy, nó cài runtime Bun và thực thi bun_environment.js ở chế độ nền
- TruffleHog được dùng để thu thập thông tin xác thực trong thư mục
$HOME
- Dữ liệu thu thập được (
contents.json, cloud.json, truffleSecrets.json, v.v.) được tải lên các repository GitHub ngẫu nhiên dưới dạng mã hóa base64 ba lớp
- Nếu có token npm, nó sẽ chỉnh sửa và phát hành lại các gói của tài khoản bị lây nhiễm để phát tán con sâu
- Nếu không có thông tin xác thực, nó sẽ cố gắng xóa thư mục home
- Các tệp chỉ dấu lây nhiễm:
setup_bun.js, bun_environment.js, .trufflehog-cache/ v.v.
Biện pháp tăng cường bảo mật
- Vô hiệu hóa hoàn toàn script npm (
ignore-scripts=true)
- Nâng cấp lên pnpm 10: script bị vô hiệu hóa mặc định, thiết lập
minimumReleaseAge (3 ngày) để trì hoãn cài đặt gói mới
- Áp dụng npm Trusted Publishers dựa trên OIDC để loại bỏ token dài hạn
- Áp dụng bảo vệ nhánh cho mọi repository
- Triển khai Granted cho AWS SSO, mã hóa token phiên
- Trên GitHub Actions, thay đổi để bắt buộc phê duyệt khi chạy workflow của contributor bên ngoài
Bài học cho các đội ngũ khác
- Cơ chế thực thi mã tùy ý khi cài npm tự nó đã là một bề mặt tấn công
- Cần đặt
ignore-scripts=true và chỉ quản lý whitelist các gói thực sự cần thiết
- Dùng pnpm minimumReleaseAge để trì hoãn cài đặt gói mới
- Bảo vệ nhánh và triển khai dựa trên OIDC là các biện pháp bảo mật bắt buộc
- Không lưu thông tin xác thực dài hạn trên máy cục bộ, chỉ cho phép triển khai qua CI
- Tiếng ồn từ cảnh báo Slack lại là chìa khóa phát hiện
Khía cạnh con người
- Lập trình viên bị lây nhiễm không có lỗi; thiệt hại xảy ra chỉ vì chạy
npm install
- Trong lúc tấn công, tài khoản đó được phát hiện đã tự động “star” hàng trăm repository ngẫu nhiên
- Sự cố này không phải lỗi cá nhân mà phơi bày lỗ hổng cấu trúc của cả hệ sinh thái
Chỉ số tóm tắt
- Từ lúc lây nhiễm đầu tiên đến đợt tấn công đầu tiên: khoảng 2 giờ
- Thời gian kẻ tấn công duy trì truy cập: 17 giờ
- Thời gian phá hoại kéo dài: 10 phút
- Mất 5 phút để phát hiện, 4 phút để chặn
- Hoàn tất khôi phục toàn bộ trong 7 giờ
- Repository bị clone: 669 / Nhánh bị ảnh hưởng: 199 / PR bị đóng: 42
Tài nguyên tham khảo
- Socket.dev: Shai-Hulud Strikes Again V2
- Báo cáo phân tích từ PostHog, Wiz, Endor Labs, HelixGuard
- Tài liệu về npm Trusted Publishers, pnpm
onlyBuiltDependencies, minimumReleaseAge, Granted
3 bình luận
Có vẻ như
pnpmvốn có cấu trúc mà theo mặc định phải cho phép từngpost-installriêng lẻ, nhưng rồi cuối cùng nhà phát triển cũng vô thức cho phép luôn.Tôi hiểu là vì
npmđược đặt để chạy mặc định, nên họ đã chuyển sangpnpmvà tắt mặc định đó để tăng cường bảo mật.Ý kiến Hacker News
Chạy
npm installkhông phải là cẩu thảVấn đề là hệ sinh thái cho phép thực thi mã tùy ý trong quá trình cài đặt package
Nhưng thất bại bảo mật ở mức nền tảng là việc dùng một package manager cho phép bên thứ ba đẩy mã vào sản phẩm của tôi mà không có bất kỳ bước kiểm chứng nào
Rốt cuộc chúng ta đang phụ thuộc vô hạn vào thiện chí và năng lực của các package manager và những người vận hành chúng
Ngoài ra có vẻ OP đang ám chỉ rằng thông tin xác thực đã được lưu ở dạng văn bản thuần trên filesystem
Ở cấp độ ngôn ngữ, có thể xây dựng một cấu trúc giới hạn mã chỉ được đọc input, tiêu tốn tài nguyên và chỉ tạo ra output đúng kiểu
Không giải quyết trọn vẹn bài toán chuỗi cung ứng, nhưng sẽ giảm phạm vi phơi nhiễm đi rất nhiều
Kiểu như “một người dùng công cụ này thì không sai, nhưng nếu ai cũng dùng thì hệ sinh thái có vấn đề”
Việc nhiều công cụ cho lập trình viên vốn đã kém an toàn là điều đã được chứng minh nhiều lần
Nếu thực sự quan tâm thì phải thể hiện bằng hành động
Khi dùng VS Code, thật phiền khi chỉ để thêm một chức năng nhỏ mà phải cài một plugin không rõ do ai làm ra
Cuối cùng chẳng phải vẫn sẽ là một cấu trúc buộc phải chạy mã mà ta phải tin tưởng sao
netrccho xác thực httpNếu dùng git qua http thì kiểu đường lộ thông tin xác thực dạng plain text này gần như luôn tồn tại
Một năm trước, maintainer của pnpm đã đề xuất “chặn script post-install theo mặc định”
Từ góc nhìn người dùng thì sẽ bất tiện, nhưng về lâu dài tôi tin đây là thay đổi mà mọi người đều sẽ thấy đáng giá
PR liên quan: pnpm/pnpm#8897
Rốt cuộc đây lại là thêm một ví dụ nữa cho thấy tính tiện lợi đã thắng bảo mật
Họ nói “cơ sở dữ liệu không bị xâm phạm”, nhưng nếu kẻ tấn công đã truy cập được AWS và secret thì theo tôi như vậy đã là bị xâm phạm rồi
Nếu đã có khả năng truy cập thì nên được coi là xâm phạm
Sau khi mã độc được thực thi thì gần như không thể truy vết nguồn gốc
pnpm installcũng hoàn tất bình thường nên rất khó phát hiệnNếu có EDR như Sentinel One hay CrowdStrike thì có lẽ đã có thêm nhiều đầu mối để điều tra
Đoạn “đã clone tổng cộng 669 repo” khá gây chú ý
Tôi thắc mắc một công ty có chưa đến 100 nhân viên mà có hơn 600 repo thì có bình thường không
So với quy mô đội ngũ thì thâm niên và vòng đời dự án ảnh hưởng đến số lượng repo nhiều hơn
pnpm đã ngừng tự động chạy lifecycle script như preinstall rồi, nên có vẻ họ đã dùng bản cũ
Xem PR liên quan
postinstallpnpm chặn script của dependency, nhưng vẫn chạy script ở cấp project
Cảm ơn vì đã chia sẻ bản phân tích sự cố sau sự việc (post-mortem) một cách minh bạch
Những trường hợp như thế này rất quan trọng với toàn ngành
Tôi tò mò liệu traffic tấn công có thể phân biệt được với traffic phát triển thông thường hay không
Bên tôi cũng đang muốn siết chặt egress filtering trong môi trường dev, nhưng
npm installhay bị hỏng nên vẫn đang đau đầuTôi đang suy nghĩ về bảo mật git trên laptop cá nhân
Hiện tại tôi để SSH key ở máy local và push
Tôi cũng có quyền admin nên khá rủi ro. Muốn biết có cách nào an toàn hơn không
Làm vậy có thể tách riêng khóa ký và khóa truy cập, còn tài khoản quản trị thì có thể quản lý riêng
Tài liệu liên quan: 1Password SSH Agent, Git Commit Signing, GitHub OAuth, GitHub CLI Login
Ưu điểm là khóa không thể bị đưa ra ngoài, nhưng nếu mã độc chạy trên máy của tôi thì vẫn còn rủi ro
Ví dụ Linux, ví dụ macOS
Có thể dùng TPM hay Yubikey để làm cho việc tấn công khó hơn đôi chút, nhưng không thể phòng thủ hoàn toàn
Tác vụ quản trị nên được thực hiện trên một máy chuyên dụng riêng
gpg-agentcho xác thực SSHKhi push hay commit thì nhập PIN để mở khóa
Commit mang tên Torvalds là một dấu hiệu nhận diện (signature) thường thấy sau khi bị lây nhiễm
Bài phân tích chính thức của Microsoft cũng có nhắc tới
Con worm này rất ồn ào, và một số kẻ tấn công đã lợi dụng thông tin xác thực bị lộ để biến repo riêng tư thành công khai hoặc chỉnh sửa readme nhằm mục đích quảng bá
Tôi tự hỏi nếu kẻ tấn công không phá hoại gì mà chỉ âm thầm lấy cắp thông tin thì liệu kiểu phát hiện này có còn khả thi không