通过CDN漏洞攻陷数千个网站:unpkg与tar库的安全隐患

本文详细分析了unpkg.com CDN服务中由于tar库实现缺陷导致的任意文件写入漏洞,攻击者可利用符号链接和硬链接覆盖其他npm包文件,最终在PNC银行、React.js等数千个网站上执行恶意JavaScript代码。

通过CDN漏洞攻陷数千个网站

Max Justicz
2018年5月23日

摘要:unpkg.com是一个流行的CDN服务,用于托管npm包资源。我在其tar解压实现中发现了一个漏洞,允许攻击者在服务器上写入任意文件(包括其他包的文件)。若被利用,此漏洞可使攻击者在数千个网站(包括PNC银行、React.js主页和内布拉斯加州政府网站)上执行恶意JavaScript。切勿完全信任第三方CDN——请使用子资源完整性(SRI)和哈希锁定!

漏洞详情

当用户请求类似https://unpkg.com/react@16.3.2/的URL时,unpkg会检查是否已在/tmp/unpkg-react-16.3.2/目录下下载并解压该包。若未找到,则从npm拉取对应的tar文件。

解压后,unpkg允许读取包内的任何文件。例如,访问https://unpkg.com/react@16.3.2/package.json可获取react的package.json文件,而访问https://unpkg.com/react@16.3.2/则可查看整个包的目录列表。

以下是unpkg用于解压tar文件的代码片段:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function ignoreSymlinks(file, headers) {
  return headers.type === "link";
}

function extractResponse(response, outputDir) {
  return new Promise((resolve, reject) => {
    const extract = tar.extract(outputDir, {
      readable: true, // 所有目录/文件应可读
      map: stripNamePrefix,
      ignore: ignoreSymlinks
    });

    response.body
      .pipe(gunzip())
      .pipe(extract)
      .on("finish", resolve)
      .on("error", reject);
  });
}

此代码的第一个问题是:它并未如声称的那样忽略符号链接。在该tar库中,符号链接条目的header.typesymlink而非link。因此,攻击者可通过创建指向/的符号链接,并利用Web界面浏览目录,实现服务器上的任意文件读取。

第二个问题是:即使headers.type正确检查了symlink,由于tar库的ignore函数实现问题,下述主要攻击仍可生效。

首次漏洞利用尝试

在本地unpkg实例上,我利用此漏洞读取了/proc/self/environ,该文件会输出Web服务器进程的环境变量。这些变量中包含一个Cloudflare API密钥,攻击者可能利用其通过API进行恶意DNS操作(此为未经测试的假设——我不确定Cloudflare是否支持限制API密钥权限)。

不幸的是(或幸运的是?),Heroku环境的某些特性导致我无法在真实unpkg服务器上读取/proc/self/environ。推测原因与服务器返回的错误HTTP Content-Length有关:读取/proc/self/environ时,本地实例报告Content-Length: 0,但仍返回了文件内容。Heroku的智能反向代理可能检测到Content-Length: 0并截断了响应体。

服务器返回Content-Length: 0是因为stat /proc/self/environ返回大小为0,而unpkg使用此值设置该头部。

第二次漏洞利用尝试

此时我因未能找到接管服务器的方法而略显沮丧。我向unpkg维护者报告了符号链接问题后便休息了。

但随后我开始深入思考tar文件:我们能解压文件到文件夹、创建符号链接……能否将文件解压到已提取的符号链接所指向的目录?我使用十六进制编辑器制作了一个tar文件,尝试创建指向/tmp的符号链接link,然后尝试将文件解压到link/oops.txt

我原以为任何成熟的tar实现都不会允许此操作,果然在笔记本电脑上解压失败:

1
2
3
4
5
6
$ tar -xvf symlink-oops.tar 
exploit/
exploit/link
exploit/link/oops.txt
tar: exploit/link/oops.txt: Cannot open: Not a directory
tar: Exiting with failure status due to previous errors

但unpkg未使用GNU Tar,而是使用了名为tar-fs的包。而tar-fs愉快地解压了此归档文件。

于是我们成功了!由于我们能在Web服务器用户权限范围内写入(和覆盖)任意文件,我们可以覆盖其他包专用目录中的文件(如/tmp/unpkg-react-16.3.2/)。为测试此漏洞,我制作了一个包的两个版本,并让第二个版本覆盖第一个版本的文件(测试成功)。

比预期更严重的漏洞

许多tar实现还支持解压硬链接。由于创建指向目录的硬链接通常是无效操作,我制作了原始漏洞利用的变体:

  1. 创建指向已知存在文件的硬链接foo
  2. 解压名为foo的常规文件(内容任意)

果然,tar-fs对此攻击同样脆弱,只要我有适当权限且知道文件在文件系统中的位置,就能覆盖文件。

tar-fs维护者报告此变体后,他次日早晨回复时显得有些担忧。出乎意料的是,更流行的tar库node-tar也对硬链接变体脆弱。tar-fs维护者与我提交了错误报告,node-tar也迅速打了补丁。

哦,如果你需要一个深度防御的教科书案例,请记住:npm客户端(使用pacote因而依赖node-tar)未受此攻击影响,唯一原因是pacote开发者有先见之明,决定从不解压硬链接或软链接。

结论

如果你的JavaScript CDN开始提供恶意软件,你和你的用户会多惨?完全沦陷?因此,请自行托管文件或使用子资源完整性(SRI)。它允许你锁定要加载文件的加密哈希,在现代浏览器中防范此类攻击。

感谢unpkg.com、tar-fs和node-tar的维护者快速修复这些漏洞。

无耻推广:如果你厌倦了#鸟类网站并想使用真正尊重自由的社交网络,应考虑加入Mastodon!这是一个联邦式社交网络,其分布式工作原理类似电子邮件。加入联邦宇宙(fediverse),帮助我们构建友好的安全社区!


联系信息
Max Justicz
max@justi.cz
mastodon.mit.edu/@maxj

我会在仅几篇文章后放弃此博客吗?敬请关注!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计