3 điểm bởi GN⁺ 2025-09-20 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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
Quảng cáo

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

 
GN⁺ 2025-09-20
Ý 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

    • Tại sao token npm của angulartics2 lại có quyền phát hành tinycolor?
      Nghe đúng như kiểu đường tấn công tổ chức rất cổ điển
      “Những món nợ cũ” từng tạo ra trong quá khứ rồi sẽ có ngày quay lại cắn mình
      Vài năm trước tổ chức của chúng tôi cũng từng có chuyện như vậy
      Một lỗ hổng bảo mật còn sót lại trong trình biên tập cũ đã thay ba đời vẫn cho phép upload lên server
      Không phải kiểu có thể tìm ra hết lúc build, mà cuối cùng lại bị phát hiện bằng quét URL

    • 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

    • Hoàn toàn đồng ý
      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 audit là mặc định nên dùng

  • Publish cục bộ dựa trên 2FA là không bền vững
    Tại sao cách publish cục bộ dùng 2FA lại không bền vững?
    Vấn đề thật sự là workflow publish tự động
    Tôi nghĩ phần lớn package npm không cần publish quá thường xuyên hay quy trình phát hành quá phức tạp
    Con người tự bật 2FA rồi chạy npm publish thì có gì mà quá khó?
    Nếu đến mức như vậy cũng thấy phiền thì nên đánh giá lại số lượng package mình đang quản lý

    • Cũng có lý
      Ý 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