星の空

由于 GitHub Workflow 编写不当导致的大规模更新异常与潜在的安全隐患

· Soulter

前言

GitHub Workflow 是一个非常强大的工具,可以帮助我们自动化很多工作,它能够在检测到我们推送的提交、合并请求等事件时,自动新建一个虚机来执行我们预先编写好的脚本。

我的很多项目都使用了这个功能来帮助我执行自动化流程,比如这篇博客的渲染就是由 GitHub Workflow 来完成的。在我推送 Markdown 格式的博客内容后,它会首先签出到我最新的提交,然后调用 hugo 的工具链来渲染成 HTML 页面,然后将打包好的文件夹压缩,通过 scp 指令发送到我位于日本东京的生产服务器 tokyo-neko 上,再执行相关操作后,网站上就会显示我写的博文——整个流程小于 1 分钟。而我要做的,只是专注于写作,然后 git push。

此外,我使用它来自动发布 hugging-face-api的新版本到 PyPi 平台上,以及自动构建 AstrBot Docker 镜像。

而本篇文章要记录的,就是由于对 GitHub Workflow 中某个插件的不熟悉导致的 AstrBot Docker 端更新业务的大规模异常。

问题的发现

起因是群里有部分用户反馈 Docker 的 AstrBot 更新后无法正常运行。

排查之后,推送了一个修复版本,但是在测试尝试更新的时候,发现可视化面板页面一直显示 “正在检查更新”。

用户也反馈遇到了这个问题。

于是我使用 docker logs 查看了日志,发现显示了这么一句话:

Username for 'https://github.com': 

这是 AstrBot 在调用 git 指令时,需要输入用户名的提示。

然而,AstrBot 是 GitHub 上的一个 Public 的项目,拉取代码是不需要输入用户名的。

问题的分析

我猜测是 GitHub Workflow 在签出仓库时,将我的账号的某些令牌数据写入了某个 .git/ 下的配置文件,然后令牌过期了,导致了这个问题。下面的实验也佐证了我的猜测。

我重新构建了一次 Workflow,代码签出到早期版本的 AstrBot。然后在测试机上拉取该新构建的镜像来运行,执行更新操作,发现可以正常更新——说明肯定是有一个令牌的机制导致了这个问题。

对 .git 文件夹的深入分析

.git 文件夹是所有使用 git 进行版本管理的项目都会有的,它存储了 git 的一些配置信息,比如分支信息、提交信息等。由于以 . 开头,因此他在大多数文件资源管理器中是隐藏的。具体来说,.git 文件夹包含有如下文件:

  • HEAD:指向当前签出的分支的引用,一般内容是 ref: refs/heads/main
  • COMMIT_EDITMSG:存储了最近一次提交的 message 文本。
  • FETCH_HEAD:存储了最近一次 fetch 的信息:commit_id、分支名、仓库地址(remotes)等。
  • ORIG_HEAD:存储了最近一次 HEAD 的 commit_id。
  • discritpion:存储了仓库的描述信息。
  • config:存储了 git 的配置信息
  • hooks/:存储了 git 的钩子脚本,用于在 git 的生命周期中执行一些操作,比如 pre-commit、pre-push 等。默认情况下,这个文件夹下有一些示例脚本:
    • pre-commit.sample
      pre-push.sample
      ...
      
  • index:存储了暂存区的信息,是一个二进制文件。
  • logs/:存储了 git 的日志信息,比如 HEAD 的变更记录。
  • refs/:存储了 git 的引用信息,比如分支、标签等。
  • objects/:存储了 git 的对象信息,比如 commit、tree、blob 等,是 git 的核心。

而我们问题的罪魁祸首就出现在这个 .git 文件夹下的 config 文件中。

config 文件内容以 ini 格式存储,包含了 git 的一些配置信息。

比如我的博客项目的 config 文件内容如下:

[core] // 核心配置
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
[submodule "themes/paper"] // 子模块配置
        url = https://github.com/nanxiaobei/hugo-paper
        active = true
[remote "origin"] // 远程仓库配置
        url = https://github.com/Soulter/SoulterBlogv3.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"] // 分支配置,记录了当前分支的一些信息
        remote = origin
        merge = refs/heads/main

而在我调研异常版本的 AstrBot 的 config 文件中,发现了这么一段:

http "https://github.com/" 节下的内容是我在 GitHub 上的令牌,这个令牌是使用 GitHub Workflow 的 actions/checkout@v2 这一个插件时,自动在 .git/config 文件中写入的。

而我在 docker build 指令中,直接将 AstrBot 下的所有文件打包进了镜像,包括了 .git 文件夹。

这就导致了用户在使用该镜像时,与 git 之间的交互都走的是我的账号!当令牌过期后,git 将该令牌发送给 github,而 github 识别到这是一个过期令牌,就返回需要重新验证账户的响应。这是一个巨大的安全隐患,如果被恶意利用,我的账号可能就不复存在了。

解决方案

直接使用 git clone 指令拉取代码,以代替 GitHub Workflow 的 actions/checkout@v2 这一个插件。