聚焦源代码安全,网罗国内外最新资讯!
编译:奇安信代码卫士
数字化时代,软件无处不在。软件如同社会中的“虚拟人”,已经成为支撑社会正常运转的最基本元素之一,软件的安全性问题也正在成为当今社会的根本性、基础性问题。
随着软件产业的快速发展,软件供应链也越发复杂多元,复杂的软件供应链会引入一系列的安全问题,导致信息系统的整体安全防护难度越来越大。近年来,针对软件供应链的安全攻击事件一直呈快速增长态势,造成的危害也越来越严重。
为此,我们推出“供应链安全”栏目。本栏目汇聚供应链安全资讯,分析供应链安全风险,提供缓解建议,为供应链安全保驾护航。
注:以往发布的部分供应链安全相关内容,请见文末“推荐阅读”部分。
概言之,预防措施如下:
1、为内部包使用scopes
2、在项目 root 中使用 .npmrc 文件,设置预期注册表。
3、在使用代理时特别留心。
4、 快速响应 build 失败情况。
在 npm 中,一个 “scope” 是指程序包名称开头以 @ 为前缀的名称。例如,“@my-company/foo” 就是一个 scoped 包。使用 scoped 包和使用 package.json 和 JavaScript 代码中的其它模块名称一样。
{
"name": "@mycompany/foo",
"version": "1.2.3",
"description": "just a scoped package name example",
"dependencies": {
"@mycompany/bar": "2.x"
}
}
// es modules style
import foo from '@mycompany/foo'
// commonjs style
const foo = require('@mycompany/foo')
Scopes 的引入时间是2014年,受所有热门 npm 客户端支持。
为什么以及如何使用 scopes
Npm 公共注册表上的 scoped 程序包仅可由与其相关的用户或组织机构发布,而在该 scope 内的程序包可以是私人的。另外,scope 名称可与既定注册表相关联。
例如,登录命令将确保所有 @mycompany scope 下的程序包请求将会向 https://registry.mycompany.local 注册表提出。其它与 scope 无关的请求将会进入默认注册表中。
$ npm login --scope=mycompany --registry=https://registry.mycompany.local
如此,将 ~/.npmrc 文件中的登录信息保存如下:
@mycompany:registry = https://registry.mycompany.local/ //registry.mycompany.local:_authToken = xyzabc123-arbitrary-token-value
之后,转向npm 公开注册表,并以 “mycompany” 名称创建一个免费的组织机构。此时,无人能够在公开注册表上以 @mycompany scope 发布任何内容,而且如果 build 配置错误,那么 build 就会出现 404 错误,而不是静默地提取不受信任的内容。这个步骤很重要,因为它可阻止攻击者劫持公开注册表中的组织机构名称,从而导致同样问题。
作为最终的预防措施,可在项目 root 中创建 .npmrc 文件,只需一行代码即可:
@mycompany:registry = https://registry.mycompany.local/
借此,在和该项目协作时,npm 将把 scope 和内部注册表关联在一起。
额外奖励:避免非恶意失败
使用 scopes 阻止额外失败,虽然损害并非恶意造成的,但可能令人研发并难以调试。例如,比如你正在使用内部程序包 foo。之后,公开注册表上的一名用户选择名称 foo 作为公开程序包。其他人发布了取决于 foo(公开版本而非内部 foo)的公开程序包 bar。
某天,你尝试在内部环境中使用 bar(公开包)。它告知npm 称需要一个依赖关系 foo。Npm 负责任地提取了 foo,而且由于它配置为指向内部注册表,因此它找到了内部包 foo。遗憾的是,它和公开包 foo 完全不同,于是你的 build 崩溃了。
Scopes 还会在有人不慎向公开的 npm 注册表中发布私有包时阻止泄露的发生。如果没有人位于公开组织机构中,那么即使他们在某种程度上搞砸了配置,由于无法向该 scope 发布,因此发布也不会成功。
虽然这些失败并不一定总是恶意的,但让人生厌。没有人会喜欢消防演习。使用 scopes,避免麻烦。
Scopes 关闭该攻击向量(并非所有攻击向量)
虽然软件安全并非非黑即白的问题,但 scope 能够缓解由所述特定攻击向量造成的风险,即攻击者声称正在内部使用的公开注册表上的名称。
如果你使用的 npm 私有注册表实现并不支持 scopes,则需要告知供应商并让他们知道你需要这个功能以确保供应链的安全。
如果你从未用过scopes,则迁移包名称是个大工程。同时,虽然你这样做安全才做出了这种重要的迁移,但你也可以做其它的事情。
使用配置为代理源自公开注册表的任何程序包的内部注册表是常见操作。你把私有程序包发布到该注册表中,因此它“跟踪了“公开注册表。
如果你正在使用 scopes,则可能没有问题,否则必须特别留心。
不要代理内部包名称
首先,确保内部注册表未代理已发布在其中的任意包名称。在这种攻击中,恶意用户可能会在公开注册表中发布与你的其中一个内部程序包名称一样的程序包。如果你的注册表并未代理任何该名称的内容,则只要你向内部注册表提出请求则就是受保护的状态。
尤其是,确保你的私有注册表并未配置为从上游公开注册表“合并“ 相同名称的显示 (manifest)。有时,启用该配置是为了解决解析碰撞问题,但这样做很不好,因为“解决解析碰撞”恰恰就是名称劫持 exploit 的工作原理。如有可能,则使用甚至不具备该功能的私有注册表实现。
虽然你仍然易受非恶意解析碰撞的影响,处理成本可能代价高昂且令人生厌,但你不会容易遭攻陷。
将项目配置为使用内部注册表
第二,确保所有的内部项目在项目 root 中都拥有一个 .npmrc 文件,设置内部注册表的注册表值。
registry = https://registry.my-company.local/
这样做将避免开发人员或 build 环境检查仓库并运行 npm install、不明智地提取不受信任内容以及阻止任意人员不慎在公开注册表中发布程序包的问题。
只要运行如下命令,则可随时检查当前配置的注册表:
npm config get registry
内部程序包应该是不可变的
在内部代理注册表中发布程序包后,如果程序包已删除,则你没有静默地回退到公开注册表就非常重要。
例如,你可能在内部注册表中发布了 mycompany-foo。一段时间后,你决定不再使用该程序包并从内部注册表中将其删除。如果该代理之后从公开注册表提供这一包名称,则攻击者可接管该名称并访问被遗弃的任意系统。
这就是为何 npm 公开注册表具有严格规定,说明了可以删除的软件包、软件包何时可被删除、以及永远不可复用软件包名称和版本号。
被代理数据被视为不受信任数据
除了作为默认配置值外,npm CLI 并不会特殊处理 npm 公开注册表。如果覆盖该配置设置,则 npm 将使用所提供的值,而你会减少外部内容侵入的一个向量。
然而,内部代理应当被视作和所代理数据一样的信任度。如果已通过本地权限包将其配置为合并上游显示,或者允许代理内部软件包名称,则易受攻击。
如果项目是按照上述建议配置的,则更容易看到一个404错误而非提取不受信任的内容。
不要忽视这些错误!如果 build 失败,则将系统配置为尽可能醒目的崩溃,且如发生这种情况则马上修复。供应链攻击依赖的是尚未修复的问题。如果忽视这些警报,则意味着你忽视了解决问题的强大工具。
确保软件供应链尽可能安全的工具有很多,不过最重要的是确保使用了可用工具并了解做决策时的舍与得。
https://github.blog/2021-02-12-avoiding-npm-substitution-attacks/
题图:Pixabay License
本文由奇安信编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的
产品线。
觉得不错,就点个 “在看” 或 "赞” 吧~
本文始发于微信公众号(代码卫士):如何避免 npm 替换攻击?
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论