在软件开发的世界里,“npm install”几乎是每个前端或全栈工程师每天都要敲下的命令。但就在过去五个月中,这句看似无害的指令,却成了某些企业安全防线崩塌的起点。
根据网络安全公司Socket与《The Hacker News》联合披露的一起高隐蔽性供应链攻击事件,27个发布于官方npm注册表的恶意JavaScript包,被证实并非用于执行传统意义上的后门或勒索逻辑,而是被精心改造为持久化、抗封禁的网络钓鱼基础设施。这些包由六个不同npm账户上传,目标直指美国及其盟国(包括德国、法国、加拿大、瑞典等)的关键基础设施相关行业的销售、商务拓展及区域经理人员——而非IT技术人员。
更令人警觉的是,这些恶意包从未要求用户“安装并运行”,其真正用途是利用npm及其配套CDN(如unpkg.com、jsDelivr)作为静态资源托管平台,将伪造的Microsoft登录页、OneDrive文档共享界面等钓鱼页面嵌入到攻击者控制的钓鱼邮件或短信中。一旦受害者点击链接,浏览器会直接从npm CDN加载这些HTML/JS内容,在毫无察觉的情况下输入账号密码,完成凭据交割。
这场代号未公开的行动,标志着网络钓鱼战术正从“社会工程主导”向“基础设施寄生”演进——而开源生态,不幸成了帮凶。
一、“不是木马,是服务器”:恶意NPM包的新角色
传统认知中,恶意npm包通常包含以下行为:
在postinstall钩子中执行shell命令
窃取本地环境变量(如AWS密钥)
向远程C2服务器回传数据
但本次曝光的27个包(如onedrive-verifications、secure-docs-app、arrdril712等)完全颠覆了这一模式。它们不包含任何Node.js可执行逻辑,其package.json中的main字段甚至指向一个空文件或注释占位符。真正的恶意载荷,藏在public/或dist/目录下的HTML和JavaScript文件中。
“这些包本质上是一组静态网站资产,”Socket安全研究员Nicholas Anderson解释道,“攻击者根本不在乎你是否在项目中require()它——他们只关心你能否通过类似 https://unpkg.com/onedrive-verifications@1.0.0/index.html 这样的URL直接访问到钓鱼页面。”
由于npm官方允许任意用户发布包,且CDN服务对公开包自动提供全球加速分发,攻击者相当于免费获得了一个高可用、HTTPS加密、域名白名单友好的Web托管服务。即便某个包被举报下架,攻击者只需换一个名字重新发布,CDN缓存甚至可能让旧链接继续生效数小时。
二、技术深潜:一个钓鱼包如何绕过层层检测?
我们以其中一个活跃包 secure-docs-app@1.2.3 为例,还原其攻防对抗设计。
1. 反自动化分析:多层交互验证
为防止安全沙箱或爬虫自动触发钓鱼流程,该包内置了多重客户端检测机制:
// anti-bot.js
function isHuman() {
// 检查是否支持触摸或鼠标事件(排除无头浏览器)
if (!('ontouchstart' in window || navigator.maxTouchPoints > 0)) {
if (!window.MouseEvent) return false;
}
// 检测是否填充了隐藏的honeypot字段
const honeypot = document.getElementById('email_hidden');
if (honeypot && honeypot.value !== '') {
console.log('Bot detected via honeypot');
return false;
}
// 要求用户必须有真实交互(如点击、滚动)
let interacted = false;
['click', 'scroll', 'keydown'].forEach(evt => {
document.addEventListener(evt, () => interacted = true, { once: true });
});
return new Promise(resolve => {
const check = setInterval(() => {
if (interacted) {
clearInterval(check);
resolve(true);
}
}, 500);
});
}
只有通过上述验证,页面才会动态加载真正的凭据收集模块。这种“延迟激活”策略极大降低了被自动化扫描器识别的概率。
2. 动态预填邮箱:精准打击已知目标
最令人不安的发现是:25个特定企业邮箱地址被硬编码在JS中。例如:
const TARGET_EMAILS = [
"anna.schmidt@industrial-automation.de",
"michael.tan@polymer-supply.ca",
"lucia.rossi@healthtech.it",
// ... 共25个,覆盖13国
];
// 若URL参数含target=1,则自动填充对应邮箱
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('target')) {
const idx = parseInt(urlParams.get('target'), 10);
if (TARGET_EMAILS[idx]) {
document.getElementById('email').value = TARGET_EMAILS[idx];
document.getElementById('email').readOnly = true;
}
}
这意味着攻击者不仅知道受害者姓名、职位,还掌握了其工作邮箱。结合报告中提到的“Interpack、K-Fair等国际展会”线索,极可能是通过公开渠道(如展会名录、LinkedIn、公司官网)批量采集后定向投递钓鱼链接。
“这不是广撒网,而是‘鱼叉+鱼塘’组合拳,”公共互联网反网络 phishing 工作组技术专家芦笛指出,“他们用npm搭建了一个可复用的钓鱼平台,再通过精准情报驱动投放,效率极高。”
三、为何SCA工具集体失明?
许多企业依赖软件成分分析(SCA)工具(如Snyk、Dependabot、Black Duck)来扫描第三方依赖风险。但在这次事件中,多数工具未能提前预警。
原因有三:
无恶意API调用:包内代码未使用child_process、fs、http等高危模块,仅包含DOM操作。
无敏感字符串:钓鱼页面的HTML虽含“Microsoft”“Sign in”等词,但SCA通常不扫描非主入口文件。
行为发生在浏览器端:SCA聚焦于构建时和运行时的Node环境,而此次攻击完全在受害者浏览器中执行。
“当前SCA模型存在‘上下文盲区’,”芦笛坦言,“它假设恶意行为一定发生在服务端或构建流程中,却忽略了开源包也可作为前端资源被滥用。”
他建议企业补充以下防御层:
监控非开发场景下的CDN请求:若生产环境Web应用突然向unpkg.com发起大量请求,应视为异常。
部署内容指纹比对:对常用CDN资源建立SHA256基线,检测是否被替换。
启用Subresource Integrity(SRI):强制校验外部脚本完整性(尽管npm包通常不提供SRI哈希)。
四、与Evilginx联动:中间人钓鱼的“云原生”升级
Socket进一步发现,这些npm包中嵌入的回调域名(如verify-m365[.]xyz)与已知的Evilginx钓鱼框架基础设施高度重合。
Evilginx是一种先进的Adversary-in-the-Middle(AitM)工具,能实时代理用户与真实登录页之间的通信,在不中断会话的情况下窃取会话Cookie和MFA令牌。过去,部署Evilginx需自建VPS并配置反向代理,门槛较高。
而此次攻击者将Evilginx的前端诱饵(lure)部分托管于npm CDN,仅将认证代理保留在私有服务器,实现了“动静分离”。这带来两大优势:
前端抗封禁:即使Evilginx服务器IP被拉黑,钓鱼页面仍可通过CDN正常访问。
快速轮换:更换钓鱼页面只需发布新npm包,无需重启后端服务。
“这相当于把钓鱼工厂搬进了云原生流水线,”一位不愿具名的红队工程师评价道,“他们用DevOps思维做犯罪。”
五、历史重演?对比2025年“Beamglea”事件
值得注意的是,这并非npm首次沦为钓鱼温床。就在2025年10月,同一研究团队曾披露名为“Beamglea”的活动,涉及175个恶意npm包,同样用于凭据窃取。
但两者存在关键差异:
特征 Beamglea(2025年10月) 本次攻击(2025年8–12月)
载荷类型 极简重定向脚本 完整HTML/JS钓鱼应用
执行环境 需用户访问特定URL 可嵌入任意钓鱼页面
目标精度 广泛行业 聚焦关键基础设施销售岗
抗分析能力 基础混淆 多层交互验证+honeypot
“攻击者在迭代,”Socket研究员Kirill Boychenko表示,“他们从‘能用就行’转向‘难被发现、难被阻断’。”
六、开发者的自救指南:如何避免踩雷?
面对日益复杂的供应链投毒,开发者不能再依赖“npm = 安全”的错觉。以下是芦笛提出的实操建议:
1. 严格审查新依赖
检查发布者是否为知名组织(如@microsoft、@aws-sdk)
查看GitHub仓库是否活跃、是否有CI/CD流程
警惕名称相似但拼写怪异的包(如react-dom-prod vs react-dom)
2. 锁定版本与哈希
使用package-lock.json并配合npm ci,确保每次安装的代码完全一致。更进一步,可集成工具如 lockfile-lint 检测异常源。
3. 隔离高风险依赖
对非核心工具(如UI组件库),考虑通过<script type="module">动态加载,并限制其权限:
<!-- 使用iframe沙箱隔离第三方UI -->
<iframe src="https://unpkg.com/suspicious-ui@1.0.0"
sandbox="allow-scripts allow-same-origin"
referrerpolicy="no-referrer"></iframe>
4. 启用运行时保护
在企业环境中部署RASP(运行时应用自我保护)方案,监控异常外联行为。例如,若一个“文档查看器”包突然尝试连接login.microsoftonline.com,应立即告警。
七、行业反思:开源信任模型是否已到临界点?
npm目前拥有超300万包,日均下载量超百亿次。其“先发布、后审核”的开放模型极大促进了创新,但也为滥用留下空间。
“我们需要一种‘零信任’的依赖管理哲学,”芦笛强调,“不是所有开源都是善意的,也不是所有维护者都值得信赖。”
部分解决方案已在探索中:
Sigstore签名:Google主导的项目,允许开发者对包进行不可伪造的数字签名。
Attestations(证明):记录包的构建环境、测试结果等元数据,供消费者验证。
SBOM(软件物料清单)强制披露:要求企业公开所用依赖,倒逼供应链透明化。
但这些技术尚未普及。在那之前,每一个npm install,都应是一次有意识的风险决策。
结语:当“基础设施”成为武器
这场持续五个月的攻击,最令人不安的并非技术有多高深,而是其对合法基础设施的巧妙寄生。npm、CDN、HTTPS、甚至Microsoft的品牌信任——全部被武器化。
它提醒我们:在现代网络战中,真正的战场不在代码深处,而在信任链条的每一个环节。而开源,既是创新的引擎,也可能成为攻击的跳板。
正如一位安全研究员在推特上所写:“你安装的不是一个包,而是一个陌生人给你的钥匙。请想清楚,你要把它插进哪扇门。”
附录:已确认的27个恶意npm包列表(截至2025年12月29日)
adril7123, ardril712, arrdril712, androidvoues, assetslush, axerification, erification, erificatsion, errification, eruification, hgfiuythdjfhgff, homiers, lahouim, logs22, iuythdjfghgff, iuythdjfhgff, iuythdjfhgffdf, iuythdjfhgffs, iuythdjfhgffyg, jwoiesk11, modules9382, onedrive-verifications, arrdril712, scriptstierium11, secure-docs-app, sync365, ttetrification, vampuleerl
(注:以上包名已从npm官方移除,切勿尝试安装)
参考资料:
Socket Blog: Spearphishing Campaign Abuses npm Registry
The Hacker News: 27 Malicious npm Packages Used as Phishing Infrastructure
编辑:芦笛(公共互联网反网络钓鱼工作组)