返回 文章 apply CMS 文章

Strix 25 分钟攻破 Baseten 生产 GitHub:旧镜像泄露管理员令牌

一个 2023 年的旧容器镜像,让 AI 代理在 25 分钟内拿到了 Baseten 生产 GitHub 的管理员权限。

容器安全供应链安全GitHub令牌泄露Docker构建历史
成长分 / 100 78 综合收获、行动、留存与影响

Strix 25 分钟攻破 Baseten 生产 GitHub:旧镜像泄露管理员令牌
为什么值得读真实案例展示容器镜像构建历史如何泄露长期有效的凭据,以及由此带来的供应链风险。

完整还原自主 AI 代理从侦察到验证权限的渗透链路,可作为安全测试与防御的参考。

关键洞察
  1. 公开的 Harbor 注册表允许匿名拉取镜像,暴露了 baseten/baseten-app 等制品。
  2. GitHub 个人访问令牌被直接展开在 Docker 构建历史的 RUN 命令中,清理文件系统层无法消除该泄露。
  3. 该令牌属于 basetenbot,拥有 repo 范围,对多个仓库具备 admin 和 push 权限。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:11649

我们正准备将我们自己和客户的数据托付给 Baseten。所以为了安全起见,我们先运行了 Strix,以确保他们是安全的。大约 25 分钟后,它拿到了一个有效的 GitHub token,拥有对 Baseten 内部仓库的仓库级管理员权限。

我们开发了 Strix,一个自主黑客代理,这当然意味着我们需要(便宜且快速的)推理能力。我们正在探索各种选择,而 Baseten 是显而易见的选择之一。它是一款出色的产品,估值达 130 亿美元,许多严肃的公司都依赖他们。

但是……我们是一家安全公司。在将我们的数据、模型或代码交给第三方之前,我们会先对他们进行扫描。我们更愿意在开始依赖该服务之前发现问题并帮助修复它(我们对几乎所有供应商都这样做,并且发现严重问题的比例很高)。

所以……我们把 Strix 指向了 *.baseten.co

并让它在没有凭据或源代码的情况下运行。

它带回来一个有效的 GitHub 个人访问令牌,属于 basetenbot

。该令牌拥有对 Baseten 主产品仓库、驱动其集群的 GitOps 仓库以及他们的 Homebrew tap 的管理员和推送权限,以及对其他私有仓库的读写权限,包括针对每个客户的具体仓库。

该镜像构建日期为 2023 年 3 月,而我们在 2026 年 7 月发现它时,该令牌仍然有效。

但在我们深入细节之前,先给 Baseten 的安全团队一些赞扬。他们确认该问题为严重级别,锁定了注册表项目,并在第二天下午之前轮换了令牌。他们很专业,处理得非常迅速(在这种情况下通常并非如此)。

Strix 究竟是如何发现的

Strix 的启动方式与任何优秀的渗透测试一样:侦察。很多时候,你基础设施上最严重的漏洞可能位于你忘记的子域名上的某个服务中(这就是为什么我们总是建议将黑盒测试与代码访问测试一起进行)!因此,Strix 枚举了主机,查看了证书日志,绘制了完整的攻击面。最终,它在 gcp-us-east4-zlw.registry.baseten.co 发现了一个 Harbor 注册表

Harbor 存储容器镜像并将仓库分组到项目中。其中一个项目是公开的。在没有任何令牌或认证的情况下,Strix 可以列出仓库、获取匿名拉取令牌,并下载实际的镜像清单和 blob。其中包括一个名为 baseten/baseten-app 的镜像

此时,很容易报告一个暴露的注册表然后继续。但公司确实会有意发布镜像,而 Strix 从不希望报告误报。重要的问题是镜像暴露会带来什么影响。

Strix 决定拉取一个镜像看看里面有什么。以下是 Strix 中该次运行的这一部分(是的,代理自己的文字听起来像 AI,原因显而易见):

如果那些 Harbor 项目本不应该是公开的,这就是真正的暴露,而不是元数据噪音。关键在于这条链路:枚举公开项目,读取制品元数据,铸造一个匿名 service/token

,其作用域为 repository:baseten/baseten-app:pull

,拉取清单,然后通过签名的存储重定向获取 blob。这证明了真正的匿名拉取访问。让分诊团队认真对待的方法是,从拉取的镜像中证明一件事:专有代码、内部二进制文件、硬编码凭据或内部主机名。

第一个有希望的线索是 baseten/baseten-app 中的一对 AWS 密钥

。Strix 尝试了一次只读的 sts:GetCallerIdentity

调用,该调用可以告诉你凭证属于哪个账户。响应是 InvalidClientTokenId

那个密钥已失效,所以 Strix 继续寻找。

然后,一个真正有效的令牌

它拉取了镜像层,运行了 TruffleHog(感谢我们的开源安全朋友!),并直接检查了镜像配置。果然:一个经典的 GitHub 个人访问令牌,就躺在 history[].created_by

中。

我不是 Docker 运行时专家,但幸运的是 Strix 是(多亏了它几乎可以调用所有人类知识)。所以它知道那个字段记录了构建步骤是如何创建的。在这个例子中,它包含了一个 RUN

命令,其中 GITHUB_TOKEN

的值被直接展开到了里面。

Strix 使用该令牌向 GitHub 发起了一次只读的 GET /user

请求,然后……瞧! 200

,账户名为 basetenbot

Docker 构建历史中的令牌,随后 GitHub 将其识别为 basetenbot。凭证已打码。

Docker 构建历史中的令牌,随后 GitHub 将其识别为 basetenbot。凭证已打码。

注意令牌是在哪里被发现的。据我所知,Docker 镜像有文件系统层,但它也有一个包含镜像及其构建历史信息的配置。该配置可以随镜像一起下载。如果构建历史中仍然包含令牌的另一份副本,那么清理凭证文件也无济于事。

而这个令牌在三年多后仍然有效。

好吧,basetenbot 能做什么?

任务尚未完成。

一个有效的令牌很有趣,但显然权限才是关键。这个令牌可能拥有 0 权限,因此影响为 0。所以 Strix 检查了该账户及其组织成员资格。GitHub 返回了 X-OAuth-Scopes: repo

,并且该账户属于 basetenlabs

GitHub 为 basetenbot 返回了 repo 范围,并将其组织列为 basetenlabs。

GitHub 为 basetenbot 返回了 repo 范围,并将其组织列为 basetenlabs。

然后它再次使用只读请求检查了各个仓库的权限:

仓库 访问权限
basetenlabs/b*** , admin: true push: true
basetenlabs/f*** , admin: true push: true
basetenlabs/h*** , admin: true push: true
basetenlabs/r*** 私有,读/写
basetenlabs/b*** 私有,读/写
basetenlabs/t*** 私有,读/写
basetenlabs/b*** 私有,读/写

在一个可公开下载的镜像中留下如此大量的访问权限,这简直疯狂。

到那时,我们已经掌握了足够的信息来报告,并确信这不是误报。我们没有克隆客户仓库、推送任何内容或更改任何配置。我们停在那里,立即写了披露邮件。

令牌是如何出现在那里的?

构建历史带有时间戳。包含该令牌的步骤运行于 2023 年 3 月 3 日。这是一个旧的构建凭据,当我们在 2026 年 7 月测试它时,它仍然拥有所有这些访问权限。

根本的错误相当常见。一个构建需要从 GitHub 获取私有依赖,于是有人将令牌作为构建参数传入。相关的模式如下所示:

1 | ARG GITHUB_TOKEN |

2 | RUN GITHUB_TOKEN=${GITHUB_TOKEN} bash -c '\ |

3 | if [[ "${GITHUB_TOKEN}" != "" ]]; then \ |

4 | git config --global --add \ |

5 | url."https://${GITHUB_TOKEN}@github.com/".insteadOf "git@github.com:"; \ |

6 | fi' |

我能理解为什么会有人写出这样的代码。你需要一个私有依赖,传入令牌,Git 完成认证,构建就能正常工作。但 Docker 可以将该构建参数记录在镜像的元数据和历史中。在本例中,它记录了实际的令牌值。Docker 明确警告过这一点

这种模式还有第二个问题:git config --global

会将经过认证的 URL 写入 Git 的配置文件中。即使你改变了令牌进入构建的方式,你仍然需要避免将其保存到镜像中。

修复方法是使用 BuildKit 秘密挂载 和不持久化凭据的临时认证。然后检查镜像的层及其历史。并且撤销旧令牌!更改 Dockerfile 对已经有人下载的镜像没有任何作用。

Strix 自行做了什么

Baseten 有一个响应迅速的安全团队,并且已经使用了 AI 安全工具。尽管如此,当我们发现这个来自 2023 年构建的令牌时,它对其产品仓库和部署仓库拥有管理员访问权限。

人们很容易关注应用程序和源代码仓库,而忘记旧的容器镜像。即使你扫描了镜像的文件,你仍然需要检查它的构建历史。

我喜欢这次扫描的地方在于,Strix 持续跟进这个发现。它找到了一个注册表,检查了是否真的能拉取镜像,测试了一个凭据并发现它已失效,在构建历史中找到了另一个凭据,并检查了那个凭据能访问什么。

我们并没有告诉它去寻找 Harbor,也没有给它任何关于令牌的提示。它在大约 25 分钟内自主完成了整个过程。

这就是我们构建 Strix 的原因。过去几周,AI 驱动的攻击变得非常可怕,我们相信保护自己的唯一方法就是不断自我攻击,在坏人之前发现这些问题(因为问题总会存在)。

披露

Baseten 处理得很好。时间线如下:

7 月 13 日,晚上 11:10:我报告了活跃的 basetenbot

令牌、公开的 Harbor 项目以及仓库权限。7月14日上午:Baseten 将 Harbor 项目设为私有。我指出该令牌本身仍然有效。7月14日下午4:34:Baseten 安全团队的 Anton 确认该问题为严重级别,并表示他们已将 Harbor 项目设为私有并轮换了令牌。他还要求我们安全删除已拉取的镜像。7月14日下午5:05:我们确认已删除,并发送了同一次扫描中发现的另外两个较低严重级别的问题。7月17日:Baseten 关闭了剩余问题。9月:我们告知 Baseten 我们计划公开披露该发现,并向他们发送了本文的草稿。

他们还给我们寄了一些 T 恤和卫衣,作为发现这个严重漏洞的感谢。

去检查你的旧镜像

如果你运行容器并使用 GitHub,这值得在你自己的基础设施中检查一下:

  • 看看别人在不登录的情况下能拉取到什么,包括旧标签和那些你很久没想过的项目。
  • docker history --no-trunc

读取构建历史,或者检查配置 blob 的history[].created_by

字段。也要检查各层。 - 把密钥从构建参数中移除。使用 secret 挂载,并确保消费这些密钥的命令不会把它们写回镜像。

  • 检查你的构建令牌实际能做什么。拉取一个依赖需要对该依赖的读权限。给该令牌授予产品和部署仓库的管理员权限会让泄露严重得多。限制权限并为其设置有效期。

并且对你自己的系统运行类似 Strix 的工具。整个扫描之所以开始,是因为我们想使用一个推理提供商。我们给它一个域名,就得到了一个 Baseten 第二天早上就能处理的严重漏洞。

AI 攻击者也能沿着这些相同路径行事。如果一个 agent 能在 25 分钟内从旧镜像中找到有效的管理员令牌,你会希望你自己的 agent 先找到它。