GitHub Actionsの脆弱性を報告して、人生初のCVEを取得した話 (CVE-2026-42298)
はじめに
こんにちは。エーアイセキュリティラボの見尾谷です。
今回、人生で初めてCVE(共通脆弱性識別子)を取得することができたので、その経緯と技術的な詳細について共有したいと思います。
取得したCVEは CVE-2026-42298 です。
対象となったのは、GitHubで3万以上のStarがついているOSS「
postiz-app」です。脆弱性の深刻度を示すCVSSスコアは
10.0(Critical)であり、リモートからの任意のコード実行およびトークンの流出が可能な状態でした。
本記事では、CVE取得までのフローと、GitHub Actionsの設定ミスが招く致命的なリスクを紹介します。
どんな脆弱性だったのか?
今回発見したのは、悪意のあるPull Request(PR)を通じてCI/CDパイプラインを乗っ取る、いわゆる「Pwn Request」と呼ばれる攻撃手法が成立する脆弱性でした。
Pwn Requestについては以下の記事を参考に学習を行いました。https://zenn.dev/aeyesec/articles/417578718dcced
根本的な原因は、
.github/workflows/pr-docker-build.yml(PR作成時にDockerイメージをビルドするワークフロー)と、フォーク元のPRから提供される
Dockerfile.devの関係性、そして権限設定の甘さにありました。
問題のワークフローは、未信頼のユーザー(フォーク元)から送られてきたPR内の
Dockerfile.devを使ってビルド処理を実行する構成になっていました。
これに加えて、このワークフローには
GITHUB_TOKENに対して
write-all相当の権限が付与されており、リポジトリへの書き込み操作が可能な状態でした。
【攻撃者が悪用した場合のインパクト】
もし攻撃者が、フォークしたリポジトリで
Dockerfile.devの中に悪意のあるコマンド(例: 環境変数の外部送信コマンドなど)を仕込み、元のリポジトリへPRを作成したとします。
すると、GitHub Actionsがその悪意のあるDockerfileをビルドしてしまい、コードが実行されます。
このとき実行環境に
write-all相当の権限を持ったトークンが存在していると、攻撃者は以下のようなことが可能になります。
- リポジトリへの任意のコードのコミット
- リリースの作成や改ざん
- PRやIssueの自由な操作
- リポジトリの完全な読み書き権限の奪取
どのようにして脆弱性を発見したのか
本脆弱性は、GitHub Actionsのアンチパターンを狙った静的解析と、安全な環境での手元検証というステップを踏んで発見に至りました。
静的解析(GitHub Searchを活用した調査)
GitHub Search はPublicリポジトリにあるコード検索ができます。ここで、Pwn Requestに該当するワークフロー[1] [2]を探すため、
pull_request_targetイベントと
github.event.pull_request.head.shaを組み合わせているコードを検索しました。
path:.github/workflows "pull_request_target" "github.event.pull_request.head.sha"
その検索結果の中に、報告対象のプロジェクト
postiz-appがヒットしました。
該当の
.github/workflows/pr-docker-build.ymlのソースコードを読んでみると、以下の危険な組み合わせが存在していることに気が付きました。
- 未信頼のユーザー(フォーク元)から送られた
Dockerfile.dev
をそのままdocker build
している。 - そのワークフローに紐づく
GITHUB_TOKEN
の権限が過剰に広く設定されている。
手元検証(PoCを作成しての確証)
ソースコードの静的解析だけでは「脆弱性かもしれない」という推測に過ぎません。実際に悪用可能かを証明し、報告の精度を上げるために、Proof of Concept(PoC: 概念実証)の作成を行いました。
手元のテスト環境に、対象プロジェクトと同じ構成のテストリポジトリを作成し、実際に「悪意のある
Dockerfile.devを含むPull Request」を再現してみました。具体的には、フォークしたリポジトリの
Dockerfile.devに以下の1行を追記し、PRを作成しました。
RUN echo $(whoami)
すると、元のリポジトリ側のワークフローが発火し、
docker buildの実行ログに
rootと表示されてしまいました。フォーク元の悪意あるコードが、元のリポジトリのCI環境上でrootユーザーとして任意のコマンドを実行できる状態であることが実証されました。
この検証で確信を得て、メンテナーへの報告に踏み切りました。
Tips: 脆弱性報告で「AIによる自動報告」だと思われないために
昨今、OSSコミュニティでは生成AIを利用した根拠のない脆弱性報告(スパム)が急増しており、メンテナー側も「これもAIの誤検知(ハルシネーション)ではないか?」と警戒しているケースが少なくありません。
スパム扱いされず、メンテナーに真剣に取り合ってもらうために、報告時に私が強く意識したポイントを紹介します。
必ず手元で検証(PoC作成)を行う
単に「ソースコードのこの部分が危険に見えます」と指摘するだけでは、AIが出力したテキストと区別がつきません。前述の通り、必ず手元のテスト環境で脆弱性かどうかを確認します。脆弱性があるという確証があれば、相手から技術的な質問が来ても堂々と回答できます。
検証結果は必ず「スクリーンショット」で提示する
手元の検証で脆弱性が発火した証拠を、必ずスクリーンショットとして添付して報告します。
「テキストによる説明」に加えて「明確なエビデンス」を提示することで、AIによる自動生成レポートではなく、本物の報告だという信頼を得やすくなります。
報告からCVE取得までのタイムライン
手元で脆弱性を検証した後、GitHubの機能(Private vulnerability reporting)を使用してメンテナーへ報告を行いました。
以下が実際のタイムラインです。
- 2026/04/22 06:31 UTC — 報告(起点)
- 2026/04/22 08:58 UTC — メンテナーが報告を受理(+約2.5時間)
- 2026/04/22 14:22 UTC — 修正・公開(+約5.5時間)
- 2026/04/26 13:09 UTC — GitHubよりCVEの発行(+約4日)
特筆すべきは、メンテナーの対応の圧倒的な早さです。
報告からわずか数時間で受理から修正、そしてアドバイザリの公開まで完了しています。人気OSSを支えるチームのセキュリティに対する意識の高さと、対応力の速さには本当に驚きました。
GitHub Actionsへのインジェクションを防ぐための防御策
今回の脆弱性報告と修正の過程を通じて、CI/CD環境をインジェクション攻撃から守るための具体的なベストプラクティスを深く学ぶことができました。
自分のリポジトリでもすぐに実践できる、強力な防御策をいくつか紹介します。
1. permissions
を contents: read
など最小限に絞る
GitHub Actionsでは、デフォルトの
GITHUB_TOKENの権限が広すぎる(
write-allになっている)場合があります。ワークフローファイル内で明示的に
permissionsを定義し、最小特権の原則を守ることが最重要です。
# 悪い例: 権限を明記していない、または write-all になっている # 良い例: 必要な権限だけを明示する permissions: contents: read # ソースコードの読み取りのみ許可
2. 実行可能なユーザーを OWNER
や MAINTAINER
に絞る
誰でもPRを作成してCIを回せる状態は危険です。必要に応じて、ワークフローの実行条件をリポジトリのオーナーやメンテナー、または特定のコラボレーターに限定することで、見知らぬ攻撃者からの実行を防ぎます。
jobs:
build:
# 実行ユーザーの権限をチェックする条件式を追加
if: github.actor == github.repository_owner || contains(fromJson('["user1", "user2"]'), github.actor)
runs-on: ubuntu-latest
3. pull_request_target
の代わりに pull_request
や workflow_dispatch
を使う
pull_request_targetを使う必要が本当にあるか、まず見直すことが最も効果的な防御策です。
フォークからのPRをトリガーにする場合、
pull_requestイベントはデフォルトで
GITHUB_TOKENが読み取り専用(read-only)に制限され、リポジトリのシークレットにもアクセスできない仕様になっています。[3]
そのため、シークレットへのアクセスやリポジトリへの書き込みが不要であれば、
pull_request_targetではなく
pull_requestに切り替えるだけで、今回のような攻撃のリスクを根本から排除できます。
また、外部PRを自動でトリガーにせず、メンテナーが内容を確認した上で手動実行できる
workflow_dispatchを活用するのも有効です。CIの自動トリガーをあえて外し、人間の判断を挟むことで、悪意のあるコードが自動でビルドされるリスクをゼロにできます。
4. github.repository
でリポジトリを制限する
pull_request_targetはフォーク元のPRをトリガーにしても、ベースブランチのコンテキストでワークフローが動作します。そのため
github.repositoryで実行リポジトリを明示的に制限することで、フォーク側でワークフローが意図せず実行される問題を防げます。
jobs:
build:
# 元のリポジトリでのみ実行させる
if: github.repository == 'myenv/myrepository'
runs-on: ubuntu-latest
5. Actionのバージョン指定を「タグ」ではなく「コミットハッシュ」にする
actions/checkout@v4のようなタグ指定は便利ですが、もしサードパーティのアクションの開発元が乗っ取られ、
v4タグの中身が悪意のあるコードに差し替えられた場合、CIを乗っ取られてしまいます(サプライチェーン攻撃)。
これを防ぐため、実行されるコードが絶対に変わらない「コミットのSHA(ハッシュ値)」で指定するのが最もセキュアな方法です。
steps: # タグ指定(便利だが書き換えられるリスクあり) # - uses: actions/checkout@v4 # ハッシュ値指定(安全) - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1のハッシュ
人生初CVEで得たこと・学び
この経験を通じて、大きな個人的な気づきがありました。今までOSSのコードを読んで「もしかしてバグ?」と思っても、「自分の勘違いかもしれない」「英語での報告のハードルが高い」と躊躇してしまうことがありました。
しかし、勇気を出して報告してみることで、プロジェクトの安全に貢献できただけでなく、メンテナーとのコミュニケーションも経験することができました。
もし、この記事を読んでセキュリティに興味を持った方がいれば、ぜひ身近なOSSのコードやCI/CD設定を読んでみてください。