【译文】第二期:npm的“漏洞”

hello,我是海海

这一期是关于npm漏洞的一篇译作。阅读时间15分钟

欢迎转载,请注明原文和作者

有任何疑惑的地方,欢迎后台留言

声明:本文观点除了笔者明确添加的表示自己的部分,其他均为原作者观点,与本人无关。

原文标题:The massive bug at the heart of the npm ecosystem

原文作者:Darcy Clarke

文章分类安全npmregistry一致性校验

核心观点:第三方库的mainfest和实际上传到包发布平台的tarball不会被校验,导致用户安装依赖时产生安全问题

本文涉及到的相关术语

  • CWE category:由开源社区维护的通用缺陷列表。有超过600个常见缺陷,包括缓冲溢出,路径索引遍历树错误,竞争状态条件,跨站脚本,硬编码密码以及不安全随机数等
  • registry:本文指npm registry,是一个公开的在线数据库,存储了开发者创建与共享的各种代码包。比如:registry.npmjs。与包发布平台有所不同,前者是一个数据库,后者是检索网站。
  • tarball:一个计算机术语,通常用来指通过tar命令创建的归档文件/压缩文件。
  • mainfest:在软件和编程中,通常指一个元数据文件,它包含了关于包和软件项目的重要信息。在本文中,与package.json有所不同:作为包的开发者,mainfest是上传文件时http请求的信息;作为包的使用者,mainfestpackage-lock.json中对应包的相关信息(版本、tarball源文件的指向等)。
  • 降级攻击:一种攻击方式,攻击者会试图使系统使用旧的、可能存在安全漏洞的软件版本,而不是最新的,已修复的最新版本。在本文中,特指恶意包强迫用户安装低版本的依赖包,间接达到攻击的目的。

本期大纲

  • npm历史回顾
  • 性能与安全的权衡——npm有什么漏洞?
  • 通过设计一个demo,说明一下
  • 这个漏洞会如何影响用户?
  • 这个漏洞影响了哪些平台?
  • 这个漏洞包含了哪些CWE category?
  • 针对这个漏洞,GitHub平台做了什么?
  • 针对这个漏洞,有什么解决方案?

npm历史回顾

nodejs生态发展至今,已有数以千万计的开发者创建了超过310万个软件包。每月下载次数超过2080亿次。

最开始,在npm社区,参与开源软件贡献的人非常少,这使得社区之间的信任度较高,因为每个人都可以贡献并检查代码。(可以理解为,参与的开发者都是出于善意的,并且他们的技术水平高)。

但是,随着时间的推移和生态系统的发展,社区的规则也在不断变化。

起初,npm项目信任(依赖)客户端校验,即在用户安装依赖时在本地校验依赖的包信息、完整性和子依赖树等。

虽说如此严重依赖客户端的做法是充满问题的,但是这种策略也促进了JavaScript工具生态系统的繁荣。

性能与安全的权衡——npm有什么漏洞?

(以下为笔者添加)这里需要介绍下我们平时开发时的流程,以便我们更好理解npm的漏洞:

开发时,当我们第一次安装依赖,此时项目没有package-lock.jsonnpm会检索并安装包,同时生成对应的mainfest——package-lock.json因此我们知道,package-lock.json除了锁定版本之外,还能提高安装效率,因为无需重新构建依赖树关系。

似乎,package-lock.json没有什么不好。但是,当其他用户通过package-lock.json安装依赖时,可能会出现package-lock.json和安装包信息不匹配的情况。详情见下文及图1-1。

图1-1

图1-1

registry数据库并不会使用tarball的内容来验证mainfest,而是依赖客户端进行一致性验证。事实上,当我编写这篇文章时,registry服务器还未解决这个问题。

如今,registry.npmjs.com允许用户通过PUT请求向相应的包URI发布包,请求body如下所示。

{
  // mainfest
  _id: <pkg>,
  name: <pkg>,
  'dist-tags': { ... },
  versions: {
    '<version>': {
      _id: '<pkg>@<version>`,
      name: '<pkg>',
      version: '<version>',
      dist: {
        integrity: '<tarball-sha512-hash>',
        shasum: '<tarball-sha1-hash>',
        tarball: ''
      }
      ...
    }
  },
  _attachments: {
    0: {
      content_type: 'application/octet-stream',
      data: '<tarball-base64-string>',
      length: '<tarball-length>'
    }
  }
}

不过,尽管已经过去了大约十五年,npm注册表的API文档仍然非常不完善(包括APIURL、请求方法、请求参数、响应格式等)。

备注:为什么API文档不完善会造成这个漏洞?

原因:node生态系统是一个假设为前提的,这个假设就是mainfest始终包含tarballpackage.json,但是如果API文档完善,就不会搞这个假设。

另外一个问题是,包的mainfest(如上述代码所示)是独立于包的package.json(附加在tarball)提交的。这两个文件的信息从未相互验证过,因此无法规范dependenciesscriptslicense等字段。我认为,tarball是唯一获得签名并具有可以离线存储和验证的完整性的证据。

备注:下文中作者通过设计了一个demo,强调了npm包不会进行验证tarbarmainfest的观点。

通过设计一个demo,说明一下

  • 1、在npmjs.com生成身份验证令牌auth token
  • 2、创建一个新项目:mkdir test && cd test/ && npm init -y
  • 3、下载3个包:npm install ssri libnpmpack npm-registry-fetch。作用分别是:防篡改、打包项目到npm、从registry数据库获取包信息、下载包
  • 4、项目下创建一个子目录pkg,它将充当我们测试包一致性的目标mkdir pkg && cd pkg/ && npm init -y
  • 5、修改pkg的内容
  • 6、在项目根目录下创建一个publish.js文件,代码如下
  • 7、根据需要,修改mainfest信息,比如删除scriptsdependencies
  • 8、运行程序:node publish.js
  • 9、导航到 registry.npmjs.com/pkgnpmjs.com/package/pkg/v/version 以查看差异,即对比数据库和检索网站间的差异,如下图1-2所示
;(async () => {
  // libs
  const ssri = require('ssri')
  const pack = require('libnpmpack')
  const fetch = require('npm-registry-fetch')
  // 步骤一:打包文件、生成一致性hash
  // pack tarball & generate ingetrity
  const tarball = await pack('./pkg/')
  const integrity = ssri.fromData(tarball, {
    algorithms: [...new Set(['sha1', 'sha512'])],
  })
  // 步骤二:创建mainfest信息
  // craft manifest
  const name = '<pkg name>'
  const version = '<pkg version>'
  const manifest = {
    _id: name,
    name: name,
    'dist-tags': {
      latest: version,
    },
    versions: {
      [version]: {
        _id: `${name}@${version}`,
        name,
        version,
        dist: {
          integrity: integrity.sha512[0].toString(),
          shasum: integrity.sha1[0].hexDigest(),
          tarball: '',
        },
        scripts: {},
        dependencies: {},
      },
    },
    _attachments: {
      0: {
        content_type: 'application/octet-stream',
        data: tarball.toString('base64'),
        length: tarball.length,
      },
    },
  }
  // 步骤三:将包发布到registry数据库
  // publish via PUT
  fetch(name, {
    '//registry.npmjs.org/:_authToken': '<auth token>',
    method: 'PUT',
    body: manifest,
  })
})()

图1-2

图1-2

补充说明:node-canvas就是其中一个“受害者”

这个漏洞会如何影响用户?

作者按照危害等级列举了以下影响:

  • (低)下载时,用户下载的包与registry对应的包信息不匹配
  • (中)下载时:用户会下载不在mainfest中出现的依赖,比如病毒包、可以绕过安全检测和其他的恶意包等
  • (高)安装后,用户执行相关scripts / 不存在的scripts可能会触发意料之外的事情。
  • (非常高)安装后,上述的恶意包会使用降级攻击。

这个漏洞影响了哪些平台?

已知的受影响的第三方组织和实体/CDN:

  • Synk:开源安全平台,它可以检测和修复开源依赖中的安全漏洞
  • CNPMJS/Chinese:npm中国镜像
  • Cloudflare:由Cloudflare提供的npm镜像服务
  • Skypack:直接在浏览器中使用npm包,无需打包或构建步骤
  • UNPKG:快速、全球可用的npm包内容的CDN
  • JSPM:可以在浏览器和Node.js中使用
  • Yarn:快速、可靠、安全的依赖管理工具,由Facebook, Google, Exponent 和 Tilde 联合推出

备注:作者的特别表扬

由于Socket Security容易受到该问题的影响(笔者对这个地方不甚了解,知道的同学可以在评论区发表你的见解),Socket团队自2022年9月5日起,明确使用tarball内的package.json文件作为标准,并要求必须显示包的准确信息(依赖项、许可证、脚本)。

Socket团队应该是在该领域第一个正确处理此问题的团队。(优秀!)

通过socket.dev网站,我们可以看到不同包的安全程度。如下图1-3所示。

图1-3

图1-3

这个漏洞包含了哪些CWE category?

  • CWE-602:由客户端实施服务端安全
  • CWE-94:代码生成控制不当
  • CWE-295:证书生成不当
  • CWE-325:缺少加密步骤
  • CWE-656:基于缺乏信息构建的安全性

针对这个漏洞,GitHub平台做了什么?

据我所知,Github首次意识到这个问题是2022年11月4日左右;经过独立研究后,我相信这个问题的潜在影响/风险实际上比最初我们认为的要更大。

我于2023年3月9日提交了一份包含我发现的HackerOne报告,Github关闭了它并表示他们于3月21日在内部处理了这个问题。

据我所知,他们没有取得任何重大进展,也没有公开这个问题。相反,实际上他们在过去6个月内减少了在npm产品上的投入,并拒绝跟进或提供任何补救工作的见解。

(笔者观点)原文作者在这里吐槽了Github对该漏洞的做法

针对这个漏洞,有什么解决方案?

Github陷入困境是可以被理解的。事实上,npmjs.com已经以这种方式运行了十多年了。npm-cli本身依赖这种机制,改变这种机制可能会影响用户的使用习惯。

具体的解决方案:

  • 确认registry受影响的部分,即通过人工或自动化的方式排查问题
  • 如果只有少数项目因为包收到了影响,可以手动修复package.json
  • 排查和日常开发可以是并行,不必担心你的开发因为这个问题被阻塞
  • 从根源上,需要尽快记录npm-registry的API信息,包括请求和相应的格式

感谢你的耐心阅读,如果觉得好的话,可以给我点个赞吗

创作不易,感谢你的支持!

创作不易,感谢你的支持!

qrcode_for_gh_020eba8b6b71_258.jpg

这是我的微信公众号,欢迎和我一起玩前端

本文使用 markdown.com.cn 排版

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYRvf3gI' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片