最近有个挺有意思的小风潮:接一个 GitHub Action,抓一个数据——你的 DEV 粉丝数、最新博客文章、Spotify 正在听的歌——然后自动提交进你的个人主页 README。好看,会自己更新,大家很喜欢。
我也喜欢。但我也没法不去看它做的另一件事:它在一个公开仓库里,发布了一份可运行的示例,展示你怎么处理密钥、你的工作流跑在什么权限下、你把令牌交给了哪些第三方。这不是徽章,这是一份侦察文档。
![]()
我来给你看看,攻击者打开你那可爱的个人主页工作流时,实际读到的是什么。
这类工作流大多长得差不多。类似这样的东西放在 .github/workflows/update-profile.yml 里:
on: schedule: - cron: "0 * * * *" workflow_dispatch:jobs: update: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Fetch stats run: node fetch-stats.js env: API_TOKEN: ${{ secrets.API_TOKEN }} - name: Commit run: | git add README.md git commit -m "update stats" || true git push看着无害。它每小时跑一次,抓一个数字,写进你的 README。直接发布,对吧?
下面是有人在探查你的账号时,从这 20 行里看到的东西。
如果你的仓库(或组织)还在用默认的 GITHUB_TOKEN 权限,那么这个令牌在任务运行期间被授予对整个仓库的读写权限——内容、issue、包,全部。这个工作流只需要写一个文件。但因为没人设置 permissions:,里面每一步——包括那段运行第三方代码的 node fetch-stats.js——都拿着一个能推送到你默认分支的令牌在跑。
攻击者不用猜你的安全姿态。缺少 permissions: 块本身就告诉他们你在用默认值,也就是最宽的范围。你等于告诉他们,锁就是出厂时配的那把。
修法是一个块:
permissions: contents: write # nothing else把它限定在任务级别,每一步就都降到最小权限。徽章照样更新。令牌再也碰不到 issue、包或 actions。
uses: actions/checkout@v3 看起来没问题。它不算错。但一个可变的 major 版本标签意味着你运行的是 v3 今天指向的任何东西——而且你等于公开宣告:我全线钉的是浮动标签。如果你链条里任何一个 action 在上游被攻破(这种事会发生——看看 2025 年 tj-actions/changed-files 事件,它在数千个仓库里泄露了密钥),你每小时的定时任务会自动拉下被投毒的版本,用上面那个令牌跑起来。
“钉在某个版本”和“钉在某个提交”不是同一句话。标签是别人可以移动的标签。SHA 不是。
修法:钉到完整的提交 SHA。
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1丑。无聊。不会在凌晨三点自己动起来。
个人主页工作流本身的触发器通常是安全的。但一个现在对你产生兴趣的攻击者会去读你其他的公开工作流——而“自动化我的主页”这股风潮,往往伴随着一整个文件夹里其他复制粘贴来的 action。他们在找的是 pull_request_target,它会在来自 fork 的 PR 上带着你仓库的密钥运行。再配上检出 PR 的头部代码,攻击者开一个 PR,就能在你自己的 runner 上把你的密钥偷走。你的主页自动化没有造成这件事——但它把你标记成了那种不读就复制工作流的人,而这正是他们继续看下去的全部理由。
API_TOKEN: ${{ secrets.API_TOKEN }} 把密钥以环境变量的形式交给了 node fetch-stats.js。于是你这个密钥的全部行为,取决于一段你大概是从 gist 上粘来的代码。fetch-stats.js 只把令牌发给你想用的那个 API 吗?还是它也会记日志、出错时 POST 到别处、或者拉进 40 个传递 npm 依赖,其中任何一个都能读 process.env?脚本里的每一个依赖,现在都在你密钥的爆炸半径内,按计划、无人值守地运行。
修法:用最小范围的令牌(只读、单一用途的 PAT——绝不用带 repo 范围的经典令牌去干一个只读公开数字的活),并且把任何碰到密钥的脚本当成安全关键代码,而不是装饰品。
你不必删掉徽章。你必须停止发布这份侦察文档。把下面这些加进你运行的每个工作流:
permissions: contents: write # the least you need, nothing more# pin every action to a SHA, not a tag# use single-purpose, read-only tokens# never pair pull_request_target with a checkout of untrusted code就这样。同样的徽章,同样的每小时更新,没有那份“这是我的攻击面,包装精美”的东西。
粉丝数这股风潮危险,不是因为粉丝数敏感。它危险,是因为它把“无人值守、定时、带令牌、你复制来却从没审计过的代码”变成了常态——而且是公开地做,配置本身就是破绽。徽章没问题。它教出来的习惯才是漏洞。
现在就去打开你的 .github/workflows/ 文件夹。如果没有 permissions: 块,而且你的 action 钉的是 @v3,你大概有五分钟的活要干——干完之后你照样还有那个很酷的个人主页。
打开你个人主页仓库的工作流——它们有显式的 permissions: 块吗,还是在用默认值?在评论里诚实说说。我很好奇,有多少个好玩徽章的仓库,离一个被攻破的 action 只差一个糟糕的日子。
我写安全和事物如何诚实地坏掉。如果这是你的领域,关注我。
模板让你快速回答 FAQ 或存储代码片段以便复用。
Expan
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.