3 điểm bởi GN⁺ 2025-10-31 | 2 bình luận | Chia sẻ qua WhatsApp
  • Đã xác nhận rằng trên kho lưu trữ NPM, hơn 100 gói độc hại dùng để đánh cắp thông tin xác thực đã được tải lên mà không bị phát hiện kể từ tháng 8, với tổng số lượt tải vượt 86.000 lần
  • Công ty bảo mật Koi báo cáo rằng chiến dịch tấn công được đặt tên là PhantomRaven đã phát tán 126 gói độc hại bằng cách lạm dụng tính năng Remote Dynamic Dependencies (RDD) của NPM
  • RDD là một cấu trúc cho phép gói tải mã phụ thuộc một cách động từ các miền không đáng tin cậy, nên không bị các công cụ phân tích tĩnh phát hiện
  • Kẻ tấn công đã dùng tính năng này để tải mã độc qua kết nối HTTP, nhưng trong metadata của gói lại hiển thị là "0 Dependencies", khiến nhà phát triển và trình quét bảo mật không nhận ra
  • Điểm yếu mang tính cấu trúc này cho thấy giới hạn trong quản lý bảo mật của hệ sinh thái NPM và rủi ro của cơ chế cài đặt tự động

Sự lan rộng của các gói độc hại trong kho NPM

  • Kẻ tấn công đã lợi dụng điểm yếu cấu trúc của kho mã NPM để tải lên hơn 100 gói đánh cắp thông tin xác thực kể từ tháng 8
    • Phần lớn các gói đã được phát tán mà không bị phát hiện, với tổng số lượt tải tích lũy hơn 86.000 lần
  • Công ty bảo mật Koi đặt tên cho vụ tấn công này là chiến dịch PhantomRaven và phân tích rằng một tính năng cụ thể của NPM đã bị lạm dụng
    • Theo Koi, trong số 126 gói độc hại, khoảng 80 gói vẫn còn tồn tại trên NPM tại thời điểm bài viết được thực hiện

Cấu trúc dễ bị tổn thương của Remote Dynamic Dependencies (RDD)

  • RDD là tính năng cho phép gói tải mã phụ thuộc một cách động từ website bên ngoài
    • Thông thường, phụ thuộc được tải từ hạ tầng đáng tin cậy của NPM, nhưng RDD còn cho phép tải qua các kết nối không được mã hóa như HTTP
  • Kẻ tấn công PhantomRaven đã dùng tính năng này để cấu hình việc tải mã từ URL độc hại, ví dụ: http://packages.storeartifact.com/npm/unused-imports
    • Những phụ thuộc này không hiển thị với nhà phát triển và trình quét bảo mật, và trong thông tin gói chỉ hiện là "0 Dependencies"
  • Do tính năng cài đặt tự động của NPM, loại mã phụ thuộc "vô hình" này sẽ được tự động thực thi

Giới hạn trong khả năng phát hiện của công cụ bảo mật

  • Oren Yomtov của Koi cho biết: “PhantomRaven là một ví dụ tinh vi về việc khai thác điểm mù của các công cụ bảo mật hiện có
    • RDD không bị phát hiện bởi các công cụ phân tích tĩnh
  • Vì vậy, kẻ tấn công có thể vượt qua khâu kiểm chứng bảo mật để phát tán mã độc

Các yếu tố làm tăng rủi ro

  • Koi giải thích rằng các phụ thuộc được tải qua RDD sẽ được tải mới từ máy chủ của kẻ tấn công mỗi lần cài đặt
    • Do không có cache hay quản lý phiên bản, ngay cả cùng một gói thì tại mỗi thời điểm cài đặt đều có khả năng bị chèn mã độc khác nhau
  • Cấu trúc tải động như vậy khiến việc xác minh tính toàn vẹn của gói trở nên khó khăn

Cấu trúc và bối cảnh của NPM

  • NPM là trình quản lý gói cho JavaScript, do npm, Inc., công ty con của GitHub, quản lý
    • Đây là trình quản lý gói mặc định của Node.js, gồm client dòng lệnh và npm registry
    • Registry lưu trữ các gói công khai và gói riêng tư trả phí, có thể tìm kiếm qua website
  • Vụ việc lần này được chỉ ra là một ví dụ cho thấy cấu trúc quản lý phụ thuộc tự động của NPM có thể bị lợi dụng để tấn công

Các đề cập khác

  • Ở cuối bài viết có nhắc đến ý kiến cho rằng cần chặn việc thực thi JavaScript không cần thiết
    • Tuy nhiên, vụ tấn công lần này được chỉ ra là trường hợp ngay cả mã JavaScript thiết yếu cũng bị lợi dụng

2 bình luận

 
developerjhp 2025-11-25

Tôi đã thử tạo một script scanner thời gian thực.

Trong path của repository đáng ngờ,
hãy nhập npx sha1-hulud-scanner.

Mã nguồn: https://github.com/developerjhp/sha1-hulud-scanner

 
GN⁺ 2025-10-31
Ý kiến trên Hacker News
  • Dạo này tôi đặt alias để chạy lệnh npm bên trong container Docker
    Làm vậy sẽ không làm lộ biến môi trường của tôi, không truy cập được các tệp ngoài thư mục hiện tại, và cũng không chạm tới các tệp cấu hình như .bashrc
    Tham khảo: Run tools inside Docker

    • Nghe có vẻ sandbox quá mức. Dù sao thì npm vẫn tải về mã tùy ý để chạy ngay lập tức
      Thay vào đó tôi khuyên dùng pnpm. Mặc định nó không chạy lifecycle script, và có thể chỉ định whitelist cho những script được phép chạy
    • Việc coi post-install script như quỷ dữ chỉ tạo ra ảo giác an toàn sai lầm
      Nếu thực sự muốn được bảo vệ, bạn phải sandbox không chỉ lúc cài đặt mà cả toàn bộ quá trình chạy
      Chỉ chặn post-install như hiện tại thì mới là giải pháp nửa vời. Tấn công chuỗi cung ứng đang ngày càng nguy hiểm hơn
    • Có quá nhiều vector tấn công. Nếu có ý đồ xấu, người ta có thể typo-squat tên của plugin hoặc LSP phổ biến để mã tự động chạy khi editor khởi động
      Nếu neovim hoặc vscode bị nhiễm thì chỉ với quyền người dùng cũng đã đủ làm được rất nhiều chuyện nguy hiểm
    • Tôi dùng sandbox-run
      Alias đơn giản thì áp dụng được với node/npm, nhưng khó dùng cho các chương trình khác. Vì còn phải mount các tài nguyên cần thiết vào container
    • Nhưng rốt cuộc bạn vẫn có thể tải xuống gói độc hại đúng không? Bản thân dependency cũng có thể đã bị nhiễm rồi
  • Tôi đã thắc mắc chuyện này từ lâu. Tại sao mọi người lại thản nhiên chạy npm trên hệ thống của mình như vậy
    Từ góc nhìn quen với build có thể tái lập như make, việc npm mỗi lần lại tải về thứ khác và cho ra kết quả khác thực sự gây sốc
    Ngay cả việc tạo CSS cũng bị trói vào dependency npm nghe đã thấy kỳ quặc. Vì vậy tôi từng thử đóng băng (freeze) toàn bộ môi trường npm trong Docker, nhưng cuối cùng có cảm giác đây là một cuộc chiến không thể thắng

    • Giờ package manager nào cũng hoạt động kiểu đó. maven, nuget, pip, npm đều vậy
      Nếu vẫn phụ thuộc vào package manager của distro như trước đây thì sẽ không thể có được hệ sinh thái phát triển nhanh như hiện nay
      Dù vậy, các package manager mới với bảo mật tốt hơn đang xuất hiện. Chỉ trích công cụ mà không hiểu lý do tồn tại của nó là không đúng
    • Phát triển frontend giống như một miền viễn Tây kiểu “trust me bro”. Quá trình tiến hóa của trình duyệt khiến mọi thứ có cảm giác như được vá víu bằng băng keo
    • Nếu bạn đã đóng băng npm bằng Docker, tôi muốn hỏi là sau mỗi lần cập nhật dependency bạn có xác minh lại môi trường đó không
      Thực ra npmpnpm vốn đã cố định dependency bằng lock file theo mặc định
    • Đây là hệ quả của việc “npm install thing” quá dễ và quá rẻ
      Rất nhiều mã nguồn mở bị lấp đầy bởi code viết để làm đẹp CV hơn là vì chất lượng, rồi cuối cùng lại được dùng để tạo ra những thứ như tracker quảng cáo của tập đoàn lớn hay ứng dụng ví điện tử
  • npm install không chỉ tải package về mà còn thực thi mã
    Các hook preinstall, install, postinstall trong package.json thực sự được chạy
    Có lý do chính đáng nào để quá trình cài đặt lại phải hợp pháp hóa việc chạy lệnh tùy ý không?
    Báo cáo liên quan: PhantomRaven npm malware
    Một trường hợp khác: blog của Socket.dev

    • Thực ra cấu trúc kiểu này đã tồn tại từ lâu ở các package manager cũ hơn như DEB, RPM
      Ví dụ, package kernel Linux sẽ chạy post-install script để tạo lại initramfs, cập nhật GRUB sau khi cài đặt
      Phần lớn package DEB/RPM đều có loại script này. Tức là đây là vấn đề ở cấp độ thiết kế
    • Vấn đề là với npm thì ai cũng có thể đăng package lên
      Distro Linux có hệ thống maintainer đáng tin cậy, thậm chí tự xây dựng root of trust dựa trên PGP
      Trong khi đó npm, pip, rubygems, cargo về bản chất chỉ là phiên bản trau chuốt hơn của “curl | bash
    • Ví dụ, dự án Mediasoup là một thư viện streaming viết bằng C++, và nó tự biên dịch source khi cài đặt
      Loại build hậu cài đặt như vậy từng là cần thiết để giảm gánh nặng bảo trì
    • Swift Package Manager cũng thực sự chạy file Package.swift
      Nhưng tôi nghe nói nó được sandbox rất chặt nên khó bị lạm dụng
      Tham khảo: tài liệu SwiftPM, PackageDescription
    • Nhân tiện, pnpm v10 mặc định vô hiệu hóa toàn bộ lifecycle script và yêu cầu người dùng tự cho phép
      Thảo luận liên quan
  • Nhìn vào các vụ tấn công npm gần đây, tôi bắt đầu tự hỏi liệu phát triển bằng npm còn an toàn nữa không
    Mỗi lần khởi tạo một dự án React là hàng trăm package được cài vào, mà tôi cũng chẳng biết chúng làm gì
    Ở backend thì tôi chỉ cài rõ ràng những package mình cần, còn frontend thì giống như chiếc hộp Pandora của lỗ hổng bảo mật

    • Thực ra hệ sinh thái của ngôn ngữ nào cũng na ná như vậy. Chỉ là npm lớn nhất nên lên tin nhiều hơn thôi
    • Tôi cài jj của Rust thì có 470 package, còn wan2gp của Python thì kéo theo 211 package. Chỗ nào cũng vậy cả
    • Hệ sinh thái JavaScript về mặt cấu trúc là rất dễ bị tấn công
      Giống vụ xz, mỗi dependency đều phụ thuộc vào một cá nhân ngẫu nhiên nào đó, và bạn phải tin rằng họ sẽ không dính social engineering
    • Càng ít dependency càng tốt. 0 dependency là hoàn hảo. Đó mới là chiến thắng thật sự
    • Nhân tiện, PyPI cũng không an toàn. Đã có trường hợp mã độc bị chèn vào package hợp pháp thông qua việc GitHub Actions bị hack
  • Mỗi lần phát triển với các framework như Angular hay Vue tôi đều thấy bất an
    Nhìn vào hàng nghìn dependency trong node_modules có cảm giác như điềm báo của thảm họa
    Chỉ cần một nhà phát triển mã nguồn mở bị phishing là mọi thứ có thể nhiễm ngay
    Hệ sinh thái JavaScript về căn bản là đang hỏng. Chỉ một cú gõ sai là có thể dính tấn công chuỗi cung ứng
    NuGet hay Maven cũng có thể gặp chuyện tương tự, nhưng bên đó có thư viện chuẩn lớn hơn nên dependency ít hơn và mang lại cảm giác kiểm soát được hơn

    • Go dùng URL của repo thay vì tên package nên giảm được typo-squatting
      Không hoàn hảo, nhưng vẫn tốt hơn một bậc
    • Deno giải quyết được kiểu vấn đề này. Đây là vấn đề mang tính cấu trúc của Node.js / npm
  • Trong 86.000 lượt tải xuống đó, phần lớn có khả năng không phải người dùng thực mà là scanner tự động hoặc bot
    Mỗi khi đăng phiên bản mới thì chỉ sau một hai ngày đã có hàng trăm lượt tải, nhưng có thể chẳng phải con người
    Tức là số người dùng thực sự bị nhiễm có thể gần như không có

    • Tôi cũng từng đăng thư viện và lúc đầu có khoảng 300 lượt/tuần, sau đó khoảng 100 lượt tải
      Cũng có nhiều vụ tấn công nhắm vào tên package do chatbot AI bịa ra bằng ảo giác. Không chỉ là thống kê đơn thuần
    • Hoặc cũng có thể là CI zombie của ai đó cứ tải đi tải lại liên tục
    • Nhưng nếu đây là đòn tấn công nhắm vào các tên package giả do LLM tạo ra, thì thực tế rất có thể đã có nhiều lập trình viên bị nhiễm
  • Xem thêm mô tả chi tiết về cuộc tấn công trong bài viết của BleepingComputer

  • Tôi tự hỏi liệu có cách nào phát hiện hoặc lọc ra các package dùng URL HTTP làm dependency trong lúc npm install hay không
    Vì payload có thể được gửi khác nhau cho từng bên yêu cầu nên scanner thông thường sẽ khó mà phát hiện

  • Với tư cách là một lập trình viên hobby, tôi đang nghĩ xem nên làm gì để phòng bị trước các cuộc tấn công chuỗi cung ứng kiểu này
    Cứ làm theo các tutorial nổi tiếng và cài dependency theo là dần dần trở nên vô cảm với chuyện bảo mật
    Tôi cũng chạy khá nhiều dịch vụ trong homelab của mình, nên lo rằng bot có thể chui vào lúc nào không hay. Nên bắt đầu từ đâu?

    • Chạy tách biệt dịch vụ trong container hoặc VM có thể cô lập thiệt hại
      Không đảm bảo hoàn hảo, nhưng vẫn tốt hơn rất nhiều so với việc toàn bộ máy chủ bị xâm nhập
    • Hãy coi mọi dependency là rủi ro bảo mật tiềm tàng, và chỉ dùng khi thật sự cần
      Tự sao chép phần mã cần thiết để dùng trực tiếp cũng là một cách học tốt và an toàn hơn
    • Dùng các bản phát hành phổ biến và đã tồn tại hơn 1 năm sẽ an toàn hơn. Nếu có vấn đề thì khả năng cao đã bị phát hiện rồi
    • Cũng có những HĐH như FreeBSD dùng package manager ở cấp hệ thống
      Với cấu trúc như vậy, có thể đảm bảo độ tin cậy ở cấp distro mà không cần hàng triệu người dùng tự mình kiểm chứng
    • Tôi ưu tiên package có hơn 1 triệu lượt tải mỗi tuần và không có dependency
      Ví dụ: Hono, Zod
      Gần đây tôi đã chuyển sang Bun, vì nó tích hợp sẵn những thứ như driver DB hay client S3 nên giảm được số lần phải tải thêm
  • Cấu trúc lấy dependency trong lifecycle hook lúc nào cũng có thể trở thành điểm bẻ lái cho tấn công
    Hiện tại có thể là bình thường, nhưng sau này nếu chủ sở hữu bị hack hoặc đổi ý thì nó có thể biến thành mã độc
    Kiểu install hook như vậy rốt cuộc là một thiết kế không bền vững