---
title: "依赖混淆的新变种：npm 包从明文 HTTP 拉取同名包"
date: 2026-11-10
slug: npm-dependency-confusion-http
canonical: https://kernelranch.com/blog/2026/11/npm-dependency-confusion-http
markdown_url: https://kernelranch.com/blog/2026/11/npm-dependency-confusion-http.md
tags: ["npm", "dependency-confusion", "security", "supply-chain"]
---这起事件记录在漏洞库编号 MAL-2026-18183 下，规模不大，但把依赖混淆（dependency confusion）的一个变种演示得非常清楚。

## 1. 攻击链

| 环节 | 具体做法 |
|---|---|
| 宿主包 | 发布在 `@conduitops` 作用域下 |
| 依赖指向 | 一个裸 IP 的明文 HTTP registry：`139.196.94.195:8080`，属阿里云地址段 |
| 被拉取的包 | 另一个作用域 `@conduit` 下的同名包 |
| 声明的发布方 | `package.json` 里写的是 `*.conduit.internal` 下的仓库、主页与问题追踪 |
| 结果 | 安装时直接下载并执行该包的 tarball，含生命周期脚本 |

## 2. 三个同时失效的防线

TLS。registry 是明文 HTTP，中间人可以直接改写响应。

完整性校验。从这种地址拉取的包没有可对照的完整性信息，npm 不会拒绝。

作用域隔离。宿主包在 `@conduitops`，被拉取的包在 `@conduit`，两个作用域不一致，但依赖解析并不因此报错。

漏洞库给出的结论很直白：凡是装过或运行过这个包的机器，都应视为完全失陷。

## 3. 为什么这种变种更阴

传统的依赖混淆，是利用公共 registry 与私有 registry 的同名包抢占。这一种更隐蔽：它不需要抢占官方包名，只需要让宿主包把某个依赖地址改成一个自己控制的 endpoint。

攻击者甚至不需要发布到 npm 公共仓库的正规位置——一个裸 IP 上的 HTTP 服务就够了。成本极低。

## 4. 排查与处置

排查：在 `package-lock.json` 或 `node_modules/.package-lock.json` 里搜 `http://` 开头的 resolved 地址，以及非 `registry.npmjs.org` 的域名或 IP。

处置：卸载该包、清理 lock 文件、轮换该环境能读到的所有凭据，然后检查安装时的网络连接记录（生命周期脚本会在这个阶段发起外连）。

长期防线有三条，成本都不高：在 npm 配置里把 registry 固定为 HTTPS 的私有代理；用 `npm config set strict-ssl true` 之外，再加 `--ignore-scripts` 的 CI 默认值；对 lock 文件里出现的非标准解析地址做正则拦截。

## 小结

依赖混淆的形态在变化：从"抢名字"变成"改地址"。防御的核心没变——安装依赖等于执行陌生代码，而"地址"也是攻击面的一部分。

一家公开漏洞库把这个包标记为"装过即失陷"，理由是它拿到了安装时执行任意代码的能力。这个判断标准值得记住：在 npm 生态里，能跑 `postinstall` 就等于能跑任何东西。

## 数据来源与检索时间

检索时间 2026-11-10。

| 来源 | URL | 对应数据 |
|---|---|---|
| Vulnerability-Lookup（CIRCL）MAL-2026-18183 | https://vulnerability.circl.lu/vuln/mal-2026-18183 | 宿主包作用域 `@conduitops`、被拉取包作用域 `@conduit`、registry 为 `139.196.94.195:8080` 明文 HTTP（阿里云段）、`*.conduit.internal` 声明、无 TLS 无完整性校验、生命周期脚本执行、"视为完全失陷"的结论 |

## 核验说明

- 字段与判断均来自该漏洞条目；IP 段归属（阿里云）也是条目原文的表述。
- 未做：没有下载该包做动态分析，也没有复现安装过程；文中"常规防御建议"部分为通用实践，不是针对该样本的验证结果。
