2019 年 9 月 18 日 08:40
npm 漏洞不容小觑,开发人员都需要重视 npm 的安全性最佳实践。为提高代码安全性,本文列出了针对开源维护人员和开发人员的十大最佳安全实践和生产力提示。
你重视 npm 漏洞吗?无论是前端还是后端开发人员,都应该重视 npm 的安全性最佳实践。开源代码的安全审查是提升安全性的关键所在,其中 npm 包的安全性应该是首要考虑因素,因为我们发现即使是官方的 npm 命令行工具也很容易受到攻击。
本文提供的这份备忘列表中,我们将列出针对开源维护人员和开发人员的十大最佳安全实践和生产力提示。我们从一个经常遇到的典型错误开始吧:那就是人们会把自己的密码添加到他们发布的npm 包里!
1. 不要把秘密发布到 npm 存储库上
无论你是使用 API令牌、密码还是其他类型的秘密,它们都很容易泄漏到源代码控制中,甚至泄露到公共 npm 存储库上的已发布包里。可能你的秘密就放在工作目录里,存在.env 后缀的文件内;这些文件本应添加到.gitignore,这样就能防止把它们提交给 SCM。但如果你直接从项目目录里发布 npm 包会发生什么呢?
npm CLI 会将项目打包到 tar 存档(tarball),然后推送到存储库上。具体哪些文件和目录会添加到 tarball 取决于下列规则:
- 如果存在.gitignore 或.npmignore 文件,则在准备发布包时,该文件中的内容会被忽略。
- 如果两个 ignore 文件都存在,则所有不在.npmignore 中的内容都将发布到存储库。经常就是这种情况把人搞糊涂了,这个问题还可能会把秘密泄露出去。开发人员可能会记得更新.gitignore 文件,但忘了更新.npmignore,结果潜在的敏感文件没有被推送到源代码控制中,但仍然包含在 npm 包里。
另一个好习惯是使用 package.json 中的 files 属性,该属性可作为白名单来用,指定要包含在要创建和安装的包中的文件数组(而 ignore 文件则用作黑名单)。files 属性和 ignore 文件可以并用,以明确哪些文件应该包含在包里,哪些应该排除在外。两者并用时,前面 package.json 中的 files 属性比 ignore 文件优先级更高。
包发布时,npm CLI 将详细显示正在创建的存档内容。为了万无一失,在发布命令中添加一个–dry-run 参数,在发布到存储库之前先看看 tarball 里面到底放了什么东西。
2019 年 1 月,npm 在他们的博客上提到他们添加了一种机制:如果他们发现一个包里有一个令牌,就会自动撤销这个令牌。
2. 强制锁文件
我们都很欢迎包锁文件的出现,它引入了:跨不同环境的确定性安装,以及跨协作团队强制执行的依赖项期望。一切都很美好!但是…如果我改动了项目的 package.json 文件但是忘了把锁文件提交到它旁边会怎么样呢?
在依赖项安装期间,Yarn 和 npm 的反应是一样的。当他们检测到项目的 package.json 和锁文件之间存在不一致时,会基于 package.json 的清单安装与锁文件中记录内容不同的版本。
这种情况对于构建和生产环境都可能存在威胁,因为它们可能会引入非预期的软件包版本,让锁文件形同虚设。
所幸有一种方法可以强制要求 Yarn 和 npm 引用锁文件,遵循锁文件指定的一组依赖项及其版本。任何不一致都将中止安装。命令行如下所示:
- 如果你正在使用 Yarn,请运行 yarn install --frozen-lockfile。
- 如果你正在使用 npm,运行 npm ci。
3. 忽略运行脚本,最小化攻击面
npm CLI 使用包运行脚本。如果你曾经运行过 npm start 或 npm test,那么你也使用了包运行脚本。npm CLI 基于程序包可以声明的脚本构建,并允许包来定义在安装到项目期间在特定入口点运行的脚本。例如,有些脚本 hook 入口可能是 postinstall 脚本,正在安装的包将按照这个脚本的顺序执行清理工作。
恶意用户可以使用这个功能在安装软件包后运行任意命令来创建或更改软件包,以执行恶意行为。我们已经发现的一些案例包括著名的,收集 npm 令牌的 eslint-scope 事件,此外还发现了 36 个对 npm 存储库发起域名抢注攻击的软件包。
你可以应用下面这些 npm 安全最佳实践,以最大程度地减少恶意模块攻击面:
始终审核你安装的第三方模块并做尽职调查,以确认其健康度和可信度。
- 不要盲目升级到新版本;尝试新的软件包版本之前先等其他人用一段时间来观察。
- 在升级之前,请务必查看升级版本的更改日志和发行说明。
- 安装软件包时,请确保添加–ignore-scripts 后缀以禁止第三方软件包执行任何脚本。
- 考虑将 ignore-scripts 添加到.npmrc 项目文件或全局 npm 配置中。
4. 评估 npm 项目的健康状况
过时的依赖项
盲目地将依赖项持续升级到最新版本并不是什么好主意,每次升级前都应该仔细查看发行说明、代码更改,并且通常应该全面测试过新版后再去升级。话虽如此,坐视依赖项版本逐渐过时而根本不去升级,或者经过很长时间才升级一次,也可能会带来不少麻烦。
npm CLI 可以向你提供有关你使用的依赖项的新鲜度信息,以及它们的语义版本控制偏移量。你可以运行 npm outdated 这条命令来查看哪些包已经过期了:
黄色的依赖项对应于 package.json 清单中指定的语义版本控制,而红色的依赖项表示有可用的更新。此外,这个输出还显示每个依赖项的最新版本。
呼叫大夫
你安装了各种各样的 Node.js 包管理器,可能还在路径中安装了许多不同版本的 Node.js,那么该如何验证健康的 npm 安装和工作环境呢?无论你是在开发环境还是在 CI 中使用 npm CLI,都必须评估一切是否按预期工作。
何不找来一位大夫呢!npm CLI 带有一个健康评估工具,可以用来诊断你的环境,确保其具备良好的 npm 交互。你可以运行 npm doctor 来检查你的 npm 设置:
- 检查官方 npm 存储库是否可访问,并显示当前配置的存储库。
- 检查 Git 是否可用。
- 查看已安装的 npm 和 Node.js 版本。
- 检查各种文件夹(例如本地和全局 node_modules,以及用于包缓存的文件夹)的权限。
- 检查本地 npm 模块缓存以校验正确性。
5. 审核开源依赖项中的漏洞
npm 生态系统是所有语言生态系统中最大的应用程序单一存储库。这个存储库和其中的无数库是 JavaScript 开发人员的工作重心所在,让他们能够利用其他人构建好的工作并将其合并到自己的代码库中。话虽如此,在应用程序中越来越多地采用开源库也会增加引入安全漏洞的风险。
许多流行的 npm 软件包都被发现了漏洞,所以如果没有对项目依赖项进行适当的安全审核,可能会给项目带来很大的风险。典型案例包括 npm request 、 superagent 、 mongoose ,甚至是安全相关的包,如 jsonwebtoken 和 npm validator 等。
仅仅在安装软件包时扫描安全漏洞是不够的,还应该简化开发人员工作流程,以便在软件开发的整个生命周期中持续保证安全性,并在部署代码后持续监控。
扫描漏洞
下面的 npm 安全最佳实践是使用 Snyk 扫描安全漏洞,使用以下命令:
复制代码
$ npm install -g snyk$ snyk test
当你运行 Snyk 测试时,Snyk 会报告它找到的漏洞并显示易受攻击的路径,以便你跟踪依赖关系树以搞明白是哪个模块引入了漏洞。最重要的是,Snyk 为你提供可操作的补救建议,你可以使用 Snyk 在存储库中打开的自动拉取请求升级到修复版本,或者在没有可用修复的情况下应用 Snyk 提供的补丁来缓解漏洞。Snyk 为易受攻击的软件包推荐可用的最小 semver 升级,这是一个智能升级功能。
监控开源库中发现的漏洞
安全工作并没有到此结束。
在部署应用程序后,应用程序的依赖项中又发现新的安全漏洞该怎么办?这就是为什么我们要把安全监控与项目开发的生命周期紧密集成在一起的原因所在。
我们建议将 Snyk 与你的源代码管理(SCM)系统(如 GitHub 或 GitLab)集成在一起,让 Snyk 主动监控你的项目并:
- 自动打开 PR 以升级或修补易受攻击的依赖项。
- 扫描并检测拉取请求,避免引入开源库中的漏洞。
如果你无法将 Snyk 集成到 SCM 中,则可以通过以下这条简单的命令监控从 Snyk CLI 工具发送的项目快照:
复制代码
$ snyk monitor
Snyk 与 npm 审核有什么不同?
- 我们推荐大家阅读由 Nearform 发布的博文,其中比较了 npm 审核与 Snyk 之间的差异。
- Snyk 的漏洞数据库通过其威胁情报系统提供有关漏洞的全面数据,提供更好的覆盖范围,并能够显示和报告尚未收到 CVE 的漏洞。例如,npm 提示的漏洞中有 72%是先被添加到 Snyk 数据库里的。在此处查看更多信息。
6. 使用本地 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"}
你的存储库已经准备就绪了!现在要发布一个包,只需使用 npm 命令 npm publish,它就准备好与世界分享了。
7. 负责任地披露安全漏洞
当发现新的安全漏洞时,如果在没有事先警告的情况下公开披露它们,或者没能为用户提供适当的缓解措施来保护他们,那么这种行为反而会带来潜在的严重威胁。
我们建议安全研究人员遵循负责任的披露计划,计划应该将研究人员与漏洞资产的供应商或维护者联系起来,提供相应的流程和指南来传达漏洞信息、告知关联方漏洞的影响和能力。在对漏洞正确分类后,供应商和研究人员应该协调制作漏洞的修复程序并商讨发布日期,以便在公开安全问题之前为受影响的用户提供升级路径或补救措施。
安全太重要了,不能亡羊补牢,也不能随意处置。我们 Synk 非常重视安全社区,并认为负责任地披露开源软件包中的安全漏洞可以帮助我们确保用户的安全和隐私。
Snyk 的安全研究团队定期与社区合作奖励漏洞的报告,例如在数百个社区中披露的 f2e-server 案例等;Snyk 还与弗吉尼亚理工大学等学术研究方密切合作,以提供专业的安全知识和能力,与供应商和社区维护者协作。
我们邀请你与我们合作,并在披露过程中提供帮助:
- 在这个网址负责任地披露安全漏洞,或者向我们发送邮件(security@snyk.io)。
- 我们的披露政策可以参阅这里。
8. 启用 2FA
2017 年 10 月 npm 正式宣布,支持使用 npm 存储库托管其封闭和开源软件包的开发人员进行双重身份验证(2FA)。
尽管现在 npm 存储库已经支持了 2FA,但似乎它的推广还是比较缓慢;一个例子是在 2018 年中期 ESLint 团队的开发者帐户被攻击者盗取,导致恶意版本的 eslint 传播,亦即 eslint-scope 事件。
紧急安全警告请分享。
今天,我们发现 eslint-scope 的版本 3.7.2 中包含了窃取你的 NPM 凭据的恶意代码。如果你使用的是 3.7.2 版,请立即采取措施。
Snyk 数据库条目
——Snyk(@snyksec),2018 年 7 月 12 日
对于 npm 最佳安全实践而言,启用 2FA 是一项简单但意义重大的步骤。存储库支持两种模式来在用户帐户中启用 2FA:
- 仅限授权——用户通过网站或 CLI 登录 npm,或执行其他操作(如更改配置文件信息)时。
- 授权和写入模式——配置文件和登录操作,以及诸如管理令牌和包的写操作,以及对团队和包可见信息的改动时。
首先为自己准备一个身份验证应用程序(例如谷歌身份验证就可以,它能安装在移动设备上),然后就可以开始了。一种简单方法是通过 npm 的用户界面来轻松启用 2FA 扩展防护。如果你喜欢用命令行,则可以使用支持的 npm 客户端版本(>=5.5.1)时输入以下命令轻松启用 2FA:
$ npm profile enable-2fa auth-and-writes
按照命令行提示启用 2FA,并保存紧急身份验证代码。如果你希望仅为登录和配置文件更改启用 2FA 模式,则可以在上面的代码中使用 auth-only 替换 auth-and-writes。
9. 使用 npm 作者令牌
每次你使用 npm CLI 登录时,系统都会为你的用户账户生成一个令牌,并对你的 npm 存储库进行身份验证。使用令牌可以在 CI 和自动化过程中轻松执行与存储库相关的 npm 操作,例如访问存储库上的私有模块或从某个构建步骤中发布新版本。
可以通过 npm 存储库网站管理令牌,也可以使用 npm 命令行客户端。使用 CLI 创建限制为特定 IPv4 地址范围的只读令牌,示例如下:
$ npm token create --read-only --cidr=192.0.2.0/24
要查看你的用户账户创建了哪些令牌,或在紧急情况下撤消令牌,你可以分别使用 npm token list 和 npm token revoke 命令。
请保护好自己的令牌,尽量避免传播给别人,以遵循这条 npm 最佳安全实践。
10. 了解模块命名约定和域名仿冒攻击
命名模块可能是创建包时要做的第一件事;取名前要注意,npm 定义了包名必须遵循的一些规则:
- 它限制为 214 个字符。
- 它不能以点或下划线开头。
- 名称中没有大写字母。
- 没有尾随空格。
- 只有小写。
- 不允许使用某些特殊字符:“~\’!()*”)’。
- 不能以 . 或 _ 开头。
- 不能使用 node_modules 或 favicon.ico。
即使你遵循了这些规则,还要注意 npm 在发布新包时使用了垃圾邮件检测机制,会给包名打分,并判断包名是否违反服务条款。如果违反了条件,存储库可能会拒绝该请求。
Typosquatting 是一种攻击方式,它利用了用户所犯的错误(例如拼写错误)。攻击者可以通过仿冒域名将恶意模块发布到 npm 存储库上,给模块取一个和现有流行模块非常相似的名称。
我们一直在跟踪 npm 生态系统中的一些恶意软件包;它们也出现在 PyPi Python 存储库中。最典型的例子包括 cross-env 、 event-stream 和 eslint-scope ,它们都曾成为恶意攻击的目标。
域名仿冒攻击的一项主要目标就是盗取用户凭据,因为任何包都可以通过全局变量 process.env 访问环境变量。我们见过的案例中,event-stream 的情况是攻击者针对开发人员,希望将恶意代码注入应用程序的源代码。
文章最后再来看看下面这些提示,减少这类攻击的风险:
- 将软件包安装说明复制粘贴到终端时要格外小心。确保在源代码存储库以及 npm 存储库中验证这确实是你要安装的软件包。你可以使用 npm info 验证包的元数据,以获取有关贡献者和最新版本的更多信息。
- 在你的日常工作流程中记得不用时随时登出 npm 用户账户,这样你的凭据就不会成为别人入侵你帐户的缺口。
- 安装软件包时,请附加–ignore-scripts 以降低任意命令执行的风险。例如:npm install my-malicious-package --ignore-scripts。
全文内容可在此下载 pdf 版本。
一定要打印出这份备忘清单并将其固定在某处,随时提醒自己;如果你是一名 Javascript 开发人员,或者只是喜欢使用 npm,你都应该遵循这些 npm 最佳安全实践。