- Nhiều gói npm có chứa gói mã nguồn mở @ctrl/tinycolor đã bị nhiễm các phiên bản độc hại; nguyên nhân là npm token bị đánh cắp thông qua quy trình GitHub Actions trong một kho lưu trữ cộng tác
- Kẻ tấn công đã dùng npm token có quyền rộng để phát tán mã độc tới khoảng 20 gói; trong số đó, @ctrl/tinycolor có tới 2 triệu lượt tải mỗi tuần nên mức độ ảnh hưởng rất lớn
- Các phiên bản bị nhiễm đã thực thi payload độc hại ở bước postinstall, và đội ngũ bảo mật của GitHub cùng npm đã phản ứng nhanh để gỡ bỏ và xử lý dọn dẹp
- Tác giả đã chuẩn bị kế hoạch tăng cường bảo mật nhằm ngăn tái diễn, bao gồm chuyển sang Trusted Publishing(OIDC), giảm thiểu quyền token, bắt buộc 2FA, và tận dụng các tính năng của pnpm
- Sự cố lần này cho thấy điểm yếu của bảo mật chuỗi cung ứng phần mềm, đồng thời là ví dụ cho thấy cần cải thiện các tính năng bảo mật và thay đổi thực hành bảo mật trong toàn bộ hệ sinh thái npm
TL;DR
- Một quy trình GitHub Actions độc hại đã được đẩy lên kho lưu trữ dùng chung để đánh cắp npm token
- Với token đó, kẻ tấn công đã phát tán các phiên bản độc hại của 20 gói, trong đó @ctrl/tinycolor có lượng tải rất lớn nên tác động lan rộng
- Tài khoản cá nhân hay repository không bị xâm nhập trực tiếp, cũng không có phishing hay cài mã độc cục bộ
- Nhờ phản ứng nhanh của đội bảo mật GitHub/npm, các phiên bản độc hại đã bị gỡ bỏ, sau đó các phiên bản sạch được phát hành lại để dọn cache
Diễn biến phát hiện sự cố (How I Found Out)
- Chiều ngày 15 tháng 9, thành viên cộng đồng Wes Todd đã báo về vấn đề qua DM trên Bluesky
- Khi đó, đội bảo mật GitHub/npm đã bắt đầu tổng hợp danh sách các gói bị ảnh hưởng và tiến hành gỡ bỏ
- Một manh mối ban đầu được chia sẻ là tên nhánh độc hại 'Shai-Hulud', lấy từ tên sandworm trong thế giới Dune
Điều thực sự đã xảy ra (What Actually Happened)
- Trong repository angulartics2 từng cộng tác từ lâu, vẫn còn một cộng tác viên có quyền admin
- npm token được lưu trong kho đó đã bị đánh cắp bởi một quy trình GitHub Actions độc hại
- Với token này, kẻ tấn công đã phát hành khoảng 20 gói, bao gồm @ctrl/tinycolor
- Đội bảo mật GitHub/npm đã nhanh chóng xóa các phiên bản độc hại, và tác giả đã phát hành lại các phiên bản mới đáng tin cậy
Tác động (Impact)
- Nếu cài các phiên bản độc hại, script postinstall sẽ chạy và tạo ra rủi ro bảo mật
- Người dùng bị ảnh hưởng được khuyến nghị tham khảo hướng dẫn ứng phó khẩn cấp của StepSecurity
Thiết lập phát hành và kế hoạch ứng phó (Publishing Setup & Interim Plan)
- Trước đây, việc phát hành tự động được thực hiện bằng tổ hợp semantic-release + GitHub Actions
- Dù đã dùng tính năng provenance của npm, điều đó vẫn không thể ngăn kẻ tấn công sở hữu token hợp lệ
- Trong tương lai, tác giả dự định áp dụng Trusted Publishing(OIDC) để loại bỏ token tĩnh
- Hiện tại, tất cả token đã bị thu hồi; đồng thời đang áp dụng thêm các biện pháp như bắt buộc 2FA, chỉ cho phép token có quyền granular, và xem xét tính năng minimumReleaseAge của pnpm
Các cải tiến lý tưởng (Publishing Wishlist)
- Cần có tùy chọn ở cấp tài khoản npm để bắt buộc Trusted Publishing dựa trên OIDC
- Cần hỗ trợ chặn phát hành khi thiếu provenance, và tích hợp hoàn chỉnh giữa semantic-release với OIDC
- Mong muốn GitHub UI cung cấp tính năng phát hành được phê duyệt thủ công dựa trên 2FA
- Ngay cả không có gói Pro, người dùng vẫn nên có thể tận dụng mức bảo vệ như GitHub Environments
- Trên trang gói npm, cần hiển thị việc có dùng script postinstall hay không và công khai lý do của các phiên bản đã bị xóa
1 bình luận
Ý kiến trên Hacker News
Kho lưu trữ đó vẫn còn GitHub Actions secret, tức là token npm có quyền publish rất rộng
Một trong những ưu điểm của Trusted Publishing là không còn cần dùng token publish có hiệu lực dài hạn nữa
Giờ chỉ dùng token được tạo ngắn hạn trong CI VM và token chỉ có hiệu lực trong 15 phút
Hiện đã được áp dụng trong nhiều hệ sinh thái như PyPI, npm, Cargo, Homebrew
Quy trình phát hành thực tế cũng trở nên dễ hơn đôi chút nên tôi khuyên mọi người hãy thử dùng
Nếu tài liệu vẫn còn thấy khó hiểu thì cứ thoải mái nhờ hỗ trợ
Các quản trị viên hệ sinh thái có vẻ rất muốn tính năng này được phổ biến rộng rãi
Xem tài liệu chính thức của Trusted Publishing
Đây là lần đầu tôi biết npm giờ đã hỗ trợ Trusted Publishing
Tin liên quan
Cuối tuần này tôi định thiết lập ngay
Giờ sẽ thật tốt nếu có một cờ đánh dấu trong kho lưu trữ cho biết đây là dự án đang dùng kiểu tính năng này
Làm vậy thì có thể dễ dàng chặn các gói phụ thuộc không sử dụng nó
Có vẻ luận điểm về việc đưa MFA (xác thực đa yếu tố) vào quy trình triển khai tự động vẫn chưa được chú ý đúng mức
Publish từ workflow CI rồi dùng lời nhắc MFA để xác nhận phát hành thì không có vấn đề gì, nhưng lần trước khi tôi xem thì phải mở một HTTPS tunnel để cung cấp mã, nên khá phức tạp
Tôi muốn npm hoặc GitHub cung cấp sẵn cách dễ dàng để nhập và xác nhận mã MFA ngay trong lúc CI chạy
Việc publish package có 2 bước: bước tải package lên npmjs và bước thực sự công khai nó cho người dùng
Hiện tại hai bước này bị gộp thành một việc
Theo tôi nên tách chúng ra, để hệ thống CI chỉ tự động build và upload
Còn để package đã upload được phát hành thật sự thì con người phải tự đăng nhập vào website npmjs để publish thủ công và vượt qua MFA
Thật ra tôi còn nghĩ có lẽ bản thân khái niệm publish package là không cần thiết
Nếu VCS là “nguồn thật sự” thì sao không dùng trực tiếp mà không cần quy trình publish riêng
Go thực sự làm như vậy
Package được import trực tiếp bằng URL và versioning cũng quản lý bằng tag
Làm vậy thì chỉ cần tin vào VCS nên giảm được bề mặt tấn công bổ sung
Không cần diff riêng các file archive mà chỉ cần kiểm tra theo từng commit
Vấn đề là nếu di chuyển kho lưu trữ thì đường dẫn import sẽ thay đổi, nhưng điều này cũng có thể xem là một dạng ưu điểm
Ngoài ra tôi không rõ lợi ích của một bước publish riêng biệt là gì
Nó giống như tàn dư từ thời xưa còn upload tar archive qua FTP
Trước đây tôi từng làm việc trên kho dùng chung angulartics2
Ở đó vẫn còn GitHub Actions secret chứa token npm có quyền publish rất rộng
Một cộng tác viên nào đó cũng có quyền với nhiều dự án, và đó được cho là lý do nhiều package bị ảnh hưởng cùng lúc
Một nhánh mới tên Shai-Hulud đã bị force push kèm workflow github action độc hại
Vì đó là cộng tác viên có quyền quản trị nên workflow chạy ngay mà không cần review và token npm đã bị lộ
Từ token bị lộ, phiên bản độc hại đã được phát hành lên 20 package
Phần lớn là các package không được dùng rộng rãi, nhưng @ctrl/tinycolor là package phổ biến với khoảng 2 triệu lượt tải mỗi tuần
Điều tôi vẫn chưa hiểu là làm sao token npm của kho angulartics2 lại có thể dùng để publish cả tinycolor
Tôi cũng có quyền quản trị trên kho npm của người khác và hầu như các bản phát hành gần đây đều do tôi làm
Sau khi thành quản trị viên, tôi muốn tiện thể sửa luôn những vấn đề tồn đọng bấy lâu nên số commit mang tên tôi cũng tăng lên
Tôi gần như đã nghiêng về phía dùng Github action để publish package, nhưng khi tự triển khai với 2FA tôi luôn lo sẽ lỡ phát hành khi chưa ở đúng trạng thái master
Vì những vấn đề như vậy mà tôi đã trì hoãn việc bàn với các quản trị viên khác, nhưng giờ thấy chuyện này xảy ra thì lại có cảm giác trì hoãn là đúng
Tôi không biết đáp án đúng là gì, nhưng giao thông tin xác thực cho bên thứ ba thì chắc chắn có vẻ không phải đáp án tốt
Xin lỗi nếu trước đó tôi giải thích chưa đủ rõ
Token này là token có quyền publish toàn cục đối với toàn bộ các package npm của tôi
Suốt 10 năm qua tôi vẫn luôn ủng hộ phát hành thủ công
Lúc nào cũng bị phản đối nhiều, nhưng dạo này có vẻ nó không còn là ý tưởng kỳ quặc nữa
Tôi biết CI/CD rất ngầu, nhưng nhìn vào vụ này và cả sự cố CF gần đây thì ngày càng có nhiều bằng chứng cho thấy chính tự động hóa lại khiến các vấn đề nghiêm trọng dễ xảy ra hơn
Hồi còn làm ở BigBank, mỗi lần deploy production phải có ít nhất năm người cùng trực và đi qua rất nhiều thủ tục, nhưng đổi lại ít nhất cũng biết chắc đang đưa cái gì lên
Không phải vì GitHub Actions hay script phát hành tự động, mà tôi nghĩ cách tự build, ký, upload tarball rồi xác minh như ngày xưa an toàn hơn nhiều
Các hệ thống phân phối phần mềm như Debian còn có thêm bước xác minh riêng, đó cũng là lý do cả Internet không bị hack sau vụ xz
Ít nhất phải bắt buộc có bước con người trực tiếp ký binary trước khi bản phát hành được publish
Vì vẫn có nguy cơ kẻ tấn công tự thêm mình làm maintainer rồi ký bằng khóa của chúng, nên cần đi kèm quản lý khóa tin cậy như trong hệ thống đóng gói bản phân phối để an toàn hơn
Nếu mô hình đe dọa của tôi dựa trên giả định rằng “chỉ cần lộ một tài khoản GitHub hay một API key là toàn bộ người dùng đều bị xuyên thủng”, thì tôi thực sự nên tự hỏi liệu điều đó có hợp lý không
Dùng 2FA cho publish cũng tốt, nhưng sẽ an toàn hơn nhiều nếu cần sự đồng ý bằng chữ ký mật mã từ nhiều tác giả
Không nên để việc chỉ một người bị xâm nhập là đủ để cuộc tấn công thành công
Nhiều package chỉ có đúng một tác giả
Yêu cầu chữ ký của nhiều tác giả cũng tốt, nhưng chỉ cần có xác minh chữ ký dưới một hình thức nào đó cho commit, tag, artifact... thì đã ngăn được phần lớn các cuộc tấn công
Hệ thống đóng gói bản phân phối hỗ trợ xác minh chữ ký rất chặt chẽ, nhưng các trình quản lý package của ngôn ngữ thì lại thiếu cơ chế như vậy
Ví dụ quy trình phát hành chính thức của runc đều được ký bằng khóa của maintainer, và khóa được lưu trên Yubikey hoặc tương tự
Các hệ thống phân phối cũng quản lý keyring riêng để xác minh source và binary chính thức
Nếu có quy trình như vậy thì tôi nghĩ cuộc tấn công lần này đã bị chặn ở nhiều lớp khác nhau rồi
CI vẫn có thể build ngay, nhưng đến bước cuối cùng thì maintainer phải tự ký trực tiếp
Nếu trình quản lý package ngôn ngữ không có workflow kiểu này thì Trusted Publishing ít nhất cũng là phương án đỡ tệ hơn
Nhưng nếu tài khoản GitHub bị chiếm đoạt, chẳng hạn bị đánh cắp cookie, thì vẫn có thể publish ngay
GitHub có hỗ trợ các thiết lập bảo mật như timeout cho Trusted Publishing, nhưng kẻ tấn công cũng có thể tắt chúng đi
Ngay cả khi tài khoản của tôi bị lộ, phía bản phân phối vẫn sẽ không chấp nhận các thay đổi dùng khóa mà tôi không ký, nên tương đối an toàn hơn
Tham khảo: tôi thuộc SUSE, nhưng mong hỗ trợ xác minh artifact sẽ được mở rộng hơn nữa ở openSUSE, Arch, Gentoo
Liên kết liên quan:
runc.keyring
keyring_validate.sh
release_sign.sh
runc.keyring của openSUSE
Tôi cực kỳ ghét token
Token thực chất chẳng khác gì mật khẩu tĩnh
Tôi nghĩ cần một phương thức xác thực ra hồn hơn
Ví dụ dùng Github làm nhà cung cấp token cho AWS là cách tương đối đáng mong muốn hơn
Cách tích hợp Github-AWS OIDC
Nhưng đây chỉ là một trường hợp ngoại lệ
Luồng OIDC giữa máy với máy có thể an toàn nếu được triển khai đúng cách, nhưng cấu hình quá phức tạp
Và rốt cuộc OIDC cũng vẫn cho cảm giác chỉ là “token phức tạp hơn”
Trong môi trường tự động hóa không có con người trực tiếp xác nhận thì lúc nào cũng sẽ còn thứ gì đó có thể bị lộ ở đâu đó, dù là token hay bộ tạo token
Ngay cả trong vụ worm lần này, OIDC cũng không phải lời giải gốc rễ
Nếu workflow GitHub bị xâm nhập thì dù có OIDC hay không, môi trường vẫn sẽ được bơm vào một danh tính tạm thời
Cuối cùng điều quan trọng là phải có hệ thống ngăn người dùng chưa được phê duyệt chạy workflow có chứa secret
Nếu muốn chia quyền thật chi tiết thì có khi giảm phạm vi quyền của token còn hiệu quả hơn OIDC
Ý nghĩa ban đầu của token là bị giới hạn về thời hạn sống và phạm vi quyền (authZ)
Nhưng trong đa số trường hợp thực tế lại không như vậy mà chỉ bị dùng như mật khẩu tĩnh
Có các lựa chọn thay thế như oauth hay biscuits cho phép giới hạn quyền chi tiết, nhưng thực tế ít được dùng
Trusted Publishing giờ đã được hỗ trợ trên nhiều package registry như npm
Tin liên quan
Như người khác đã nói, token chỉ nên được cấp sau khi có thời hạn ngắn hoặc xác thực thủ công như MFA, passphrase
Dùng mTLS (chứng chỉ client TLS) có lẽ là hướng gần với đáp án đúng nhất
Có ai biết công cụ/script công khai nào để kiểm tra package npm dễ bị tấn công không?
Trang stepsecurity có vẻ không có công cụ như vậy
Không thể chặn được mọi thứ, nhưng đưa provenance-action vào cũng là một ý hay
provenance-action
Với các vấn đề đã biết thì
npm auditlà mặc định nên dùngÝ tôi là ở kiểu publish cục bộ thì tôi hơi lo các lỗi như nhầm nhánh hoặc bỏ sót bước build
Tôi cứ nghĩ nếu một job CI force push rồi thay đổi sâu trong lịch sử git thì sẽ ra sao
Hiện trạng này đã không còn vận hành ổn nữa
Tất nhiên có thể ca ngợi lợi ích kỹ thuật của token OIDC, các giải pháp zero trust và những thứ tương tự
Nhưng một tỷ lệ đáng kể trong số các maintainer thư viện npm có hàng triệu lượt tải thực tế sẽ không quan tâm đến bảo mật cho tới khi chính họ bị hack hoặc npm chặn hẳn việc phát hành
Rồi lại xuất hiện những lời kêu gọi bất khả thi như “bỏ hết dependency và chỉ dùng standard library”
Giảm dependency là tốt, nhưng không giải quyết được gì cho vấn đề đã tồn tại
Thực tế thì hoặc là mười nghìn, một trăm nghìn người rời bỏ npm và cùng nhau viết lại mọi thứ, hoặc là npm áp đặt các quy định như 2FA, OIDC lên những package có lượng tải lớn, ai không tuân thủ thì chặn luôn việc phát hành
Bên nào khả thi hơn trong thực tế thì quá rõ
Nếu không, danh tiếng của npm sẽ chạm đáy và chỉ dẫn đến tình huống kiểu XKCD 927 mà thôi