【$12,000】在构建Firefox的服务器上实现RCE攻击

admin 2026-08-21 07:02:03 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文剖析MozillaTaskcluster未授权RCE漏洞。攻击者利用GraphQL端点未验证的filter参数,触发sift库的where操作符执行任意代码,导致Firefox构建服务器沦陷并泄露数据库与OAuth凭据。文章建议弃用sift或采用白名单限制操作符,并强调需严格审计匿名角色权限及修复生产环境占位符密钥。 综合评分: 89 文章分类: 漏洞分析,WEB安全,SRC活动,实战经验,渗透测试


cover_image

【$12,000】在构建Firefox的服务器上实现RCE攻击

原创

骨哥说事 骨哥说事

骨哥说事

2026年8月19日 10:30 上海

在小说阅读器读本章

去阅读

| | | — | | 声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。 |

#

#

防走失:https://gugesay.com/

不想错过任何消息?设置星标↓ ↓ ↓

#

无需登录,无需令牌,无需特殊请求头,仅仅是一个未被验证的筛选器参数。

HackerOne漏洞报告 #3782701 —— Taskcluster中未认证的RCE漏洞,12,000美元赏金

本篇文章基于研究人员griffinf提交给Mozilla的HackerOne漏洞报告 #3782701。所有技术功劳属于griffinf。

想象一下那些负责构建和发布Firefox的服务器。它们应该是锁定的吧?内部的、需要认证的,是那种让人感觉枯燥但安全的状态。

现在想象一下,只需向这些服务器发送一条curl命令,无需任何形式的登录,就能收到证据,证明你的代码刚刚在其Node.js进程中执行了。

这正是griffinf在Mozilla的Taskcluster部署中发现的情况。而且这并非隐藏在三层代码之下的隐蔽漏洞。它是一个公共的GraphQL端点,却愉快地将一个字符串作为JavaScript代码来执行。

哪里出了错?

Taskcluster是Mozilla用来构建Firefox的CI系统。其Web服务器在/graphql处暴露了一个GraphQL API,其查询参数之一filter,只是一个……任意格式的JSON对象。

这个filter被传递给一个名为sift的JavaScript库,该库让你能对内存中的数组编写类似MongoDB风格的查询筛选器。Sift很流行。但在这个版本中,Sift也很危险,因为它支持$where操作符。而$where不仅仅是筛选数据,它会运行代码。

这份报告直接指出了漏洞的爆炸半径:仅仅读取服务器的环境变量,就足以让你获取数据库凭据、部署自身的访问令牌、OAuth客户端密钥以及用于加密数据库的密钥。这不是部分访问。报告称其为实例的完全沦陷,包括运行Firefox CI的那个实例。

报告 #3782701 中的根本原因链,从灰色(请求)到红色(沦陷)。

根本原因逐步分析

以下是报告中详细的漏洞链,一部分一部分来看。

1. 筛选器参数未经修改直接传递给sift。 来源于 services/web-server/src/utils/sift.js :

import sift from 'sift';
export default (filter, array) => {
  if (!array) return [];
  return filter ? array.filter(sift(filter)) : array;
};

无论客户端以JSON形式发送什么作为filter,都会使用默认的操作符集成为sift(filter)。没有任何内容被剥离。没有任何内容被验证。

2. Sift将**$where**字符串转换为实际代码。 来源于 sift 17.1.3:

const $where = (params, ownerQuery, options) => {
  let test;
  if (isFunction(params)) {
    test = params;
  } else if (!process.env.CSP_ENABLED) {
&nbsp; &nbsp; test =&nbsp;new&nbsp;Function("obj",&nbsp;"return "&nbsp;+ params); &nbsp;&nbsp;// <-- 字符串变成代码
&nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp;&nbsp;throw&nbsp;new&nbsp;Error(\`In CSP mode, sift does not support strings in "$where" condition\`);
&nbsp; }
&nbsp; return new EqualsOperation((b) => test.bind(b)(b), ownerQuery, options);
};

这里有一个安全开关。CSP_ENABLED 本应使sift直接拒绝字符串形式的$where。但在这个部署中,该环境变量从未被设置。因此,sift不会抛出错误,而是将你发送的任何字符串包裹在new Function(...)中并执行它。对其正在筛选的数组中的每个元素执行一次。

3. 你甚至不需要登录,解析器就会运行。services/web-server/src/servers/credentials.js 处的凭据层在没有Authorization请求头时会调用next ()。它不会拒绝请求,只是让其作为匿名请求通过。然后filter参数会流经常规的解析器和加载器路径:

Query.expandScopes(scopes, filter)
&nbsp; -> loaders/scopes.js : auth.expandScopes({ scopes }) &nbsp;then &nbsp;sift(filter, expandedScopes)

同样的模式也出现在roleslistRoleIdshookGroupshookscurrentScopes查询中。这不是一次性的。这是整个API重复使用的一种模式。

4. 匿名角色恰好拥有所需的权限。 在firefox-ci-tc上,内置的匿名角色授予了auth:expand-scopesauth:list-rolesauth:current-scopeshooks:list-hooks:*权限。这意味着加载器内部的上游调用,即使对从未经过认证的调用者也会成功。然后sift对结果运行,并且$where函数会被触发。

expandScopes查询被证明是最可靠的触发点,因为上游处理程序只是简单地回显你发送给它的任何权限范围。因此,攻击者不需要实例上已存在任何角色或钩子。他们可以通过要求返回类似assume:anonymous这样的权限范围,来保证有一个非空数组可供筛选。

另一个使漏洞利用更容易被确认的细节是:services/web-server/src/servers/formatError.js中的错误格式化器直接将err.message转发给客户端。这为研究人员提供了一个干净、可读的输出通道,用于显示注入函数抛出的任何内容。

概念验证步骤

报告分三个阶段演示了这一点,所有阶段都是未认证的,都是针对实时的生产环境端点。

第一步,通过无害的内容证明代码执行。 研究人员发送了一个$where载荷,旨在服务器端计算一个值,并通过错误消息将其抛出。响应确认了计算确实在服务器端发生,并确认代码是在一个类型为"object"的对象内运行的,即Node的process全局对象。这排除了载荷只是被解析为静态数据的任何可能性。它确实在执行。

第二步,升级到执行Shell命令。 使用相同的$where注入点,研究人员让服务器运行一个Shell命令,并通过相同的错误通道返回其输出。响应返回了uid=1000 (node) gid=1000 (node) groups=1000(node),这意味着它不仅能执行JavaScript,还能访问生成进程和读取文件系统,通过读取passwd文件显示容器用户列表进一步确认了这一点。

第三步,读取进程环境变量。 拥有对任意Node代码的写入权限后,读取process.env就变得微不足道。报告的叙述从这里开始,不再仅仅是”酷炫的漏洞”,而是变成了”以下是如何完全接管”。转储的信息包括:实时的Postgres连接字符串、部署自身的Taskcluster访问令牌、数据库列加密密钥、Pulse消息队列凭据、登录流程的OAuth客户端密钥,以及一个会话密钥,这个密钥在代码中就是一个占位符字符串FIXME,并且仍然在生产环境中使用。

研究人员在第二个实例community-tc上确认了完全相同的漏洞,其自身也暴露了等价的凭据集。

为什么这是完全沦陷

  • 数据库访问,双向。 指向Taskcluster数据库的实时读写Postgres凭据。
  • 部署级别的访问令牌。 结合数据库访问,这实际上将整个Taskcluster实例拱手相让。
  • OAuth客户端密钥。 Firefox CI实例上的Auth0密钥,社区实例上的GitHub OAuth密钥,足以攻击登录流程本身。
  • 损坏的会话密钥。SESSION_SECRET在生产环境中是FIXME,这意味着会话Cookie可以离线伪造,完全独立于RCE漏洞。
  • 数据库加密密钥。DB_CRYPTO_KEYS解密应用程序在列级别加密存储的任何内容。
  • 消息队列凭据。 Pulse访问权限,这是Taskcluster的内部事件总线。

报告 #3782701 中一个泄露的环境变量如何分支到数据库、API、OAuth和会话

把所有这些问题叠加起来,报告自身的描述是准确的:一个未经认证的POST请求发送到/graphql,导致了集群级别的凭据访问,进而导致构建Firefox的CI所依赖的Taskcluster实例被完全沦陷。

修复方案

报告按优先级(从高到低)排列了其建议的修复方案:

  1. 完全停止在不可信输入上运行sift。 将GraphQL的filter参数映射到一个允许的字段和比较操作符的白名单上,并直接应用筛选,或者将筛选操作下推到上游服务中去执行。
  2. 如果必须保留sift,则限制其功能。 使用一个限制性的操作符集构建查询测试器,排除$where操作符,并且在sift接触任何输入之前,拒绝任何不在显式支持列表里的、以$为前缀的键。
  3. 在输入层修复。 在验证时拒绝$where,并为filter参数提供一个实际的、类型化的GraphQL输入类型,而不是一个开放的JSON标量。

报告也明确指出,设置CSP_ENABLED并不是真正的修复方案,只是一个偶然改变sift行为的脆弱标志。除了核心修复,报告还建议设置一个实际的SESSION_SECRET而不是字面占位符,并且在沙箱中运行Web服务器以阻止子进程生成,这样即使未来出现JS执行漏洞,也不会自动意味着拥有了Shell访问权限。

由于这个易受攻击的模式存在于Taskcluster的主分支中,报告指出这不是Mozilla独有的问题。任何运行Taskcluster的组织都继承了同样的风险,这就是为什么在漏洞修复时也发布了上游的安全公告。

对其他漏洞猎人的启示

  • “任意格式的JSON筛选器”参数本身就是一个危险信号。 任何接受开放式JSON并将其传递给像sift、mingo或类似库的API,在你假设它只是数据筛选之前,都要去检查一下该库支持哪些操作符。
  • 检查”未认证”请求的实际返回结果,而不仅仅是是否被拒绝。 这里的认证中间件没有拒绝缺少令牌的请求,而是静默地让其以匿名身份继续。这与返回401是完全不同的故障模式,如果你只测试正常路径,很容易错过这一点。
  • 匿名角色值得进行独立的审计。 整个漏洞利用链都依赖于匿名角色拥有真实的权限。一个拥有真实权限的默认角色,通常比配置错误但经过认证的角色更危险,因为没有人会想去审查它。
  • 错误信息是数据泄露的通道。 将err.message转发给客户端看起来无伤大雅,直到有人控制了抛出什么内容。
  • 生产环境中的占位符密码本身就是漏洞。SESSION_SECRET=FIXME出现在一个正在运行的部署中,其本身就是值得报告的问题,即使你是在寻找其他完全不同的漏洞时发现的。

原文报告: HackerOne#3782701,由研究人员griffinf提交给Mozilla

  • END –

感谢阅读,如果觉得还不错的话,动动手指给个三连吧~


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:骨哥说事 骨哥说事 骨哥说事《【$12,000】在构建Firefox的服务器上实现RCE攻击》

评论:0   参与:  0