通过CDN漏洞攻陷数千网站:unpkg的tar提取安全风险解析

本文详细分析了unpkg.com CDN服务中存在的tar文件提取漏洞,攻击者可通过符号链接和硬链接操作实现任意文件写入,最终可能导致数千网站被注入恶意代码。文章探讨了漏洞的技术细节、利用过程及防御措施。

通过CDN危害数千个网站

Max Justicz

2018年5月23日

tl;dr unpkg.com是一个流行的CDN服务,用于提供npm包中的资源。我在一个tar实现中发现了一个漏洞,允许我将任意文件写入unpkg服务器,包括其他包的目录。如果被利用,这个漏洞将允许攻击者在数千个网站上执行恶意Javascript,包括PNC银行、React.js和内布拉斯加州政府的主页。不要信任第三方CDN——使用子资源完整性并固定哈希值!

漏洞详情

当你请求像https://unpkg.com/react@16.3.2/这样的URL时,unpkg会检查是否已经在/tmp/unpkg-react-16.3.2/目录下下载并解压了该包。如果没有,它会从npm拉取相应的tar文件。

Unpkg允许你在包解压后读取任何文件。例如,要获取react的package.json文件,你可以访问https://unpkg.com/react@16.3.2/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维护者报告了这个原始漏洞的变体后,他第二天早上回复我,听起来有点担心。令人惊讶的是,node-tar,一个更流行的tar库,也容易受到硬链接变体的攻击。tar-fs维护者和我提交了一个错误报告,node-tar也很快得到了修补。

哦,如果你需要一个深度防御发挥作用的教科书示例,只需记住npm客户端(使用pacote,因此也使用node-tar)不容易受到此攻击的唯一原因是pacote开发人员做出了有先见之明的决定,从不提取硬链接或软链接。

结论

如果你的Javascript CDN开始提供恶意软件,你和你的用户会有多糟糕?完全糟糕?那么要么自己托管文件,要么使用子资源完整性。它允许你固定要加载的任何文件的加密哈希,在现代浏览器中保护你免受此类攻击。

感谢unpkg.com、tar-fs和node-tar的维护者迅速解决了这些漏洞。

无耻的插播

如果你有兴趣抛弃#birdsite并想使用一个真正尊重你自由的社交网络,你应该考虑加入Mastodon!它是一个联邦社交网络,意味着它以分布式方式工作,有点像电子邮件。加入我们在fediverse的行列,帮助我们建立一个友好的安全社区!

联系方式

Max Justicz
max@justi.cz
mastodon.mit.edu/@maxj

我会在仅几篇文章后就放弃这个博客吗?敬请关注并找出答案!

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