细数历年npm包安全问题
-
2022年3月,npm包 node-ipc以反战之名,“投毒”,node-ipc的作者不只添加了反战标语,还在旧版本10.1.1-10.1.2中添加了恶意JS文件删除俄罗斯和白俄罗斯用户文件
-
2021年11月, npm包 coa被劫持, 大量react项目被影响. 【部分公司项目,在发布时因其影响而发布失败】
-
2021年10月, ua-parser-js被恶意劫持,多个版本植入挖矿脚本
-
2019年7月,有开发者在purescript npm 安装程序中发现一些恶意代码。原来是有攻击者获取了@shinnn(purescript npm 安装程序原始作者)的npm账户,随后在purescript npm安装程序中插入恶意代码。
-
2019年6月,黑客向 npm 发布一个“useful”的包(electron-native-notify),等到它被目标(某应用程序)使用后更新包内容,加入恶意代码。而某应用程序又是 Agama 加密货币钱包的一部分。
-
2018年11月,npm 下载量超过 200 万的 package 被注入了恶意代码,黑客利用该恶意代码访问热门 JavaScript 库,目标是 copay(开源比特币钱包)及其衍生产品的用户,以此窃取用户的数字货币。
-
2018年7月,黑客利用恶意代码破坏ESLint库。该恶意代码被设计用来窃取其他开发人员的npm证书
-
2018年5月,黑客试图在名为getcookies的流行npm包中隐藏后门。
除了上述恶意的黑客导致的安全问题外,某些npm包,有意或无意的存在各种各样的漏洞或bug。常见的如:DOS, REDOS, Prototype Pollution, XSS漏洞等等。
如何避免安全漏洞
1、使用锁文件
package.json中库的版本号格式:
-
~ 匹配最近的小版本依赖包,比如~1.2.3会匹配所有1.2.x版本,但是不包括1.3.0
-
^ 匹配最新的大版本依赖包,比如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0
-
* 意味着安装最新版本的依赖包
当我们的package.json中的依赖项的版本号出现以上几种匹配格式时,会导致我们在安装依赖时出现不必要的更新,在团队之间协作时导致版本不统一的问题。
npm从版本5开始支持锁包,安装依赖时会生成一个package-lock.json的锁文件,npm install时会检测到package-lock.json的文件,然后再去安装依赖;
yarn和npm类似,安装依赖时会生成yarn.lock;
这种锁文件的机制保证了版本的稳定性,从而保证了我们使用的第三方依赖是安全的。
cnpm目前不支持锁包。
然而,有的时候会因为各种各样的问题,导致我们的lock文件并没有发挥到他的作用,比如团队协作中a同学用npm安装而b个同学使用yarn安装,a同学的锁文件package-lock.json对b同学形同虚设;
2、忽略运行脚本,最小化攻击面
npm CLI 使用包运行脚本。如果你曾经运行过 npm start 或 npm test,那么你也使用了包运行脚本。npm CLI 基于程序包可以声明的脚本构建,并允许包来定义在安装到项目期间在特定入口点运行的脚本。例如,有些脚本 hook 入口可能是 postinstall 脚本,正在安装的包将按照这个脚本的顺序执行清理工作。
恶意用户可以使用这个功能在安装软件包后运行任意命令来创建或更改软件包,以执行恶意行为。我们已经发现的一些案例包括著名的,收集 npm 令牌的 eslint-scope 事件,此外还发现了 36 个对 npm 存储库发起域名抢注攻击的软件包。
你可以应用下面这些 npm 安全最佳实践,以最大程度地减少恶意模块攻击面:
始终审核你安装的第三方模块并做尽职调查,以确认其健康度和可信度。
-
不要盲目升级到新版本;尝试新的软件包版本之前先等其他人用一段时间来观察。
-
在升级之前,请务必查看升级版本的更改日志和发行说明。
-
安装软件包时,请确保添加–ignore-scripts 后缀以禁止第三方软件包执行任何脚本。
-
考虑将 ignore-scripts 添加到.npmrc 项目文件或全局 npm 配置中。
3、审核开源依赖项中的漏洞
npm 生态系统是所有语言生态系统中最大的应用程序单一存储库。这个存储库和其中的无数库是 JavaScript 开发人员的工作重心所在,让他们能够利用其他人构建好的工作并将其合并到自己的代码库中。话虽如此,在应用程序中越来越多地采用开源库也会增加引入安全漏洞的风险。
许多流行的 npm 软件包都被发现了漏洞,所以如果没有对项目依赖项进行适当的安全审核,可能会给项目带来很大的风险。典型案例包括 npm request 、 superagent 、 mongoose ,甚至是安全相关的包,如 jsonwebtoken 和 npm validator 等。
仅仅在安装软件包时扫描安全漏洞是不够的,还应该简化开发人员工作流程,以便在软件开发的整个生命周期中持续保证安全性,并在部署代码后持续监控。
扫描漏洞
npm audit (npm@>6.x)
npm 收购nsp后推出的官方检查工具, 之前应该是基于nsp数据库
npm audit 依赖于npm package-lock.json 或 npm-shrinkwrap.json
常用方法
npm audit # 只扫描,显示有问题的依赖包
npm audit fix #扫描并自动修复和安装符合兼容性要求的包
npm audit fix --package-lock-only #只修改package-lock.json 需要手动安装依赖
npm audit fix --only=prod #扫描并修复
npm audit --audit-level=moderate #设置扫描报告等级
# 检查报告,会包含有问题的包和版本信息,以及漏洞信息,漏洞的严重程度等等
snyk
snyk是一家美国的网络安全公司,有自己的安全数据库,
漏洞数据库地址:security.snyk.io/, 包含多语言,多个包管理工具的漏洞
snyk cli 工具是该公司的开源项目github.com/snyk/snyk, 用来检查npm包存在的安全漏洞
Snyk cli工具同时支持yarn 和 npm的锁版本文件
当你运行 Snyk 测试时,Snyk 会报告它找到的漏洞并显示易受攻击的路径,以便你跟踪依赖关系树以搞明白是哪个模块引入了漏洞。最重要的是,Snyk 为你提供可操作的补救建议,你可以使用 Snyk 在存储库中打开的自动拉取请求升级到修复版本,或者在没有可用修复的情况下应用 Snyk 提供的补丁来缓解漏洞。Snyk 为易受攻击的软件包推荐可用的最小 semver 升级,这是一个智能升级功能。
基础使用方法
# 安装
npm i -g snyk
# 登录, snyk cli 依赖于snyk.io 提供的api
snyk auth
# 扫描
snyk test
监控开源库中发现的漏洞
安全工作并没有到此结束。
在部署应用程序后,应用程序的依赖项中又发现新的安全漏洞该怎么办?这就是为什么我们要把安全监控与项目开发的生命周期紧密集成在一起的原因所在。
我们建议将 Snyk 与你的源代码管理(SCM)系统(如 GitHub 或 GitLab)集成在一起,让 Snyk 主动监控你的项目并:
自动打开 PR 以升级或修补易受攻击的依赖项。
扫描并检测拉取请求,避免引入开源库中的漏洞。
如果你无法将 Snyk 集成到 SCM 中,则可以通过以下这条简单的命令监控从 Snyk CLI 工具发送的项目快照:
snyk monitor
4、使用本地npm代理,搭建npm私库
npm 存储库是最大的包集合,可供所有 JavaScript 开发人员使用,也是 Web 开发人员的大多数开源项目的主页所在地。但有时你可能在安全性、部署或性能方面有不同的需求。在这种情况下,npm 允许你切换到另一个存储库:
当你运行 npm install 时,它会自动与主存储库通信以解析所有依赖项;如果你想使用另一个存储库那也非常简单:
-
设置 npm set registry 以设置默认存储库。
-
对单个存储库使用参数–registry。
Verdaccio 是一个简单的轻量级零配置私有存储库,只需输入下面的命令就能安装它了:
npm install --global verdaccio
轻而易举就能托管你自己的存储库!下面来看看这个工具最重要的一些功能:
-
它支持 npm 存储库格式,包括私有包功能、作用域支持、包访问权限控制和 Web 界面中的用户身份验证
-
它提供了挂钩远程存储库的功能,以及将每个依赖项路由到不同存储库和缓存 tarball 的能力。你应该代理所有依赖项来减少重复下载并节省本地开发和 CI 服务器的带宽。
-
作为身份验证提供程序,它默认使用 htpasswd 安全选项,但也支持 Gitlab、Bitbucket 和 LDAP。你也可以使用自己的选项
-
可以很容易地使用其他存储提供程序来扩展
-
如果你的项目基于 Docker,使用官方镜像是最佳选择
-
它为测试环境提供了非常快的引导,并且可以方便地测试大型单一存储库项目
它运行起来相当简单:
$ verdaccio --config /path/config --listen 5000
如果你用 verdaccio 作为本地私有存储库,可以考虑配置你的包强制发布到本地存储库,避免开发人员意外发布到公共存储库上。要做到这一点,请将以下内容添加到 package.json:
“publishConfig”: {“registry”: "https://localhost:5000"}
参考: