先日起きた tj-actions や reviewdog のセキュリティインシデントのレポートを読みました。

https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/

その内容を個人的な検証結果や感想を挟みつつかいつまんで書きたいと思います。
詳細は原文を読んでください。

なお、侵害された repository 及び tag は全て修正され、盗まれた Personal Access Token (PAT) も revoke されているはずです。

攻撃の流れ

tj-actions/changed-files が侵害されるまでに複数の PAT の流出及びリポジトリの侵害が連鎖的に起こっています。
つまり tj-actions/changed-files が直接的にいきなり侵害されたというより、複数のリポジトリをいわば踏み台のようにして侵害したという感じでしょうか。
攻撃者は PAT が盗まれたりした GitHub Account とは別に複数の GitHub Account を使って攻撃を行っています。
ちなみに今回見つかった悪意のあるユーザーは全て削除されているようです(それはそう)。
まぁインシデントと同時期にアカウントが削除されているので調査で怪しいアカウントとして目をつけられたというのもあるでしょう。

  1. spotbugs/sonar-findbugspull_request_target workflow が悪用され、 secrets に登録していた PAT が流出
  2. PAT を悪用して spotbugs/spotbugs に悪意のあるユーザー jurkaofavak を招待
  3. jurkaofavak が spotbugs/spotbugs にブランチ hewrkbwkyk を一瞬作成し、即削除。その push event で悪意のある workflow が実行され、 secrets に登録していた spotbugs/spotbugs のメンテナの PAT が流出。流出した PAT は reviewdog/action-setup の write 権限を持っていた
  4. reviewdog/action-setup の Fork を作成し、 Fork に悪意のある commit を作成
  5. reviewdog/action-setupv1 tag を書き換え、 Fork repo の悪意のある commit に向ける
  6. tj-actions/eslint-changed-files が内部的に侵害された reviewdog/action-setup@v1 を実行しており、 tj-actions/changed-files のリポジトリで tj-actions/eslint-changed-files が使われていたため、侵害されたコードが tj-actions/changed-files で実行され、 Machine User tj-bot-actions の PAT が盗まれる
  7. tj-actions/changed-files の Fork を作成し、悪意のある commit を作成
  8. tj-bot-actions の PAT を使って tj-actions/changed-files の全タグが Fork の悪意のある commit に書き換えられる

攻撃者は tj-actions/changed-files に依存する coinbase/agentkit を標的にしたと思われますが、幸いにも agentkit から secret が流出したりすることはなかったようです。

Our team also discovered that the initial attack targeted Coinbase.
The payload was focused on exploiting the public CI/CD flow of one of their open source projects (agentkit) probably with the purpose of leveraging it for further compromises.
However, the attacker was not able to use Coinbase secrets or publish packages.

pull_request_target の悪用

Surprise! Pull_request_target

pull_request_target は GitHub Actions の Workflow をトリガする event の一つですが、 public repository で使うにはセキュリティ的に非常に注意が必要です。

https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/

https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-04-Poisoned-Pipeline-Execution

通常、 pull_request event では Fork からの pull request の場合 workflow 実行時に secret が渡りませんし、 GitHub Actions token も read only になります。
しかし、 pull_request_target では secret が渡りますし GitHub Actions token にも write 権限を付与できます。
workflow 自体は default branch を参照するので改竄できませんが、 workflow で実行しているリポジトリ内のスクリプトは pull request で改竄できるので、悪意のあるコードが実行できてしまいます。

そのため、 public repository では基本 pull_request_target は使わない、 使う場合は workflow 以外のリポジトリのコードを参照しないものに限定するべきでしょう。

なお、自分は以前 pull_request_target を活用してセキュリティを改善する記事を書いていますが、これはあくまで private repository を前提としているため、今回のような問題はないと思います。

https://zenn.dev/shunsuke_suzuki/articles/secure-github-actions-by-pull-request-target

reviewdog の auto invite の悪用

Adnan also pointed out that reviewdog uses an auto-invite mechanism.

reviewdog organization では contributor を自動で organization に invite し write 権限を付与する仕組みがありました。
インシデント後は止められています。

https://github.com/reviewdog/reviewdog/issues/2079#issue-2929729316

Disabled automated inviter workflow.

なお、自動 invite では reviewdog 本体への write 権限は付与されず、 reviewdog 本体が直接的に攻撃されることはなかったようです。

Note: Those contributors do not have write access to the main reviewdog repository or related core repositories (e.g., errorformat).

reviewdog は非常に人気のある OSS ですし、非常に多くの Action があります (執筆時点で 52 個の public リポジトリ)。

https://github.com/orgs/reviewdog/repositories

これらのリポジトリをメンテするのは大変ですし、メンテを継続するために積極的に write 権限を渡したくなるのも理解できます。
ただ、今回はそこを悪用されてしまった形であり、今回のインシデントによってコントリビューターに write 権限を付与するハードルが更に上がってしまったかと思います。

ユーザーからしたらインシデントが収束し自動 invite が止められたらめでたしめでたしという感じですが、メンテナからしたら今後のメンテをどうしていくのかという問題は残っており、これは難しい問題です (reviewdog どうこうというより、一般論として) 。

OSS Project を如何に持続的にメンテしていくかは常に難しい問題ですね。

Fork network の悪用

After a user forks a repository in GitHub, they can add their commits to the fork.
These commits are added to the “fork network” and can be referenced from the original repository.

元のリポジトリとその Fork は Fork network と呼ばれ、それらのリポジトリのcommitは Fork network の他のリポジトリからも参照できます。
今回の検証のためにリポジトリを 2 つ作りました。

  1. https://github.com/szksh-lab/test-action : Original
  2. https://github.com/szksh-lab-2/test-action : Fork

commit a0728ffde5a5f398ff82357930f5ad5145bdba6f は Fork repo の commit ですが、 original repo からも参照できます。

多分この黄色い警告を皆さんも見たことがあるかと思います。

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Original の action のバージョンとして Fork の commit を指定できます。

- uses: szksh-lab/test-action@a0728ffde5a5f398ff82357930f5ad5145bdba6f

しかも、 original repo に fork repo の commit を指定して tag も作れます。

https://github.com/szksh-lab/test-action/releases/tag/v0.1.0-compromised

もっとも、 tag を作れるなら original repo に commit を push することもできるので態々 fork の commit に向ける必要もないようにも思えますが、 original に commit を push する必要がなくなるのでより痕跡を残さずに済みます。
tag の書き換えは free plan では audit log に残らないらしく、そのため fork の commit に向けるようにすれば audit log に何も残さずに改竄ができます。

Fork repo を消しても Fork network に Fork repo の commit は残るので SHA を指定すれば参照できますし、どの repo の commit なのか trace するのが難しくなります。

なので、例えば Fork から action を update する PR が来てしかも version が full length commit hash で固定されているような場合、本当にその SHA がその action の安全な SHA なのか確認する必要があります。

一見 v4.2.2 のように見えて実は危険な SHA

uses: actions/checkout@> 

これを手作業で確認するのは大変ですが、 pinact には –verify option があります。 (突然の宣伝)

https://github.com/suzuki-shunsuke/pinact

--verify option をつけるとコメントに書かれた version と SHA が一致しない場合エラーになります。

無効な Email を設定して User を隠蔽

Hiding GitHub Users

攻撃者は valid なメールアドレスでアカウントを作成し、悪意のあるcommitを生成した後、 GitHub の Policy で禁止された匿名の email に変更することでその後の行動を trace できなくした可能性があるようです。
匿名の email が使われると GitHub はそのユーザーを public からは見えなくするそうです。
攻撃者はこれを悪用し、 trace を困難にしたと推測しています。

ただ、引用された Policy を見ても public から見えなくするとは書いてないですし、実際にどういう挙動になるのかは自分には良くわかりません。

https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies#4-spam-and-inauthentic-activity-on-github

commit impersonation

We assume that although the attacker had a GitHub token with write permission to the repository, they preferred to disguise their malicious commit by impersonating a valid user in a valid pull request — a technique called “commit impersonation.”

攻撃者は commit impersonation というテクニックを使って valid な他のユーザーになりすましてcommitを生成したようです。
まぁ今回の攻撃を見る限りなりすまさなくても攻撃は成立している気がしますが、後から気づかれにくくする意図があったのでしょうか?

追記: 良く考えたら後から調べたときにユーザーと紐づかない commit や deleted account と紐づく commit は怪しいし、 Renovate になりすますのは妥当な気もしてきました。まぁ Renovate でも不自然ですけど

https://github.com/0xdead8ead/gitfraud

なりすましはかなり簡単で commit 時に email を @users.noreply.github.com としてしまえばその commit は指定したユーザーに紐づいてしまいます。

git config user.email ユーザー名>@users.noreply.github.com

今回の攻撃では攻撃者は Renovate になりすましたようですが、 Renovate のような App でも renovate[bot]@users.noreply.github.com とすればなりすませてしまいます。

この対策としては Branch Ruleset で commit への署名を必須にするのが有効です。
自分の場合、ほぼ全てのリポジトリの全ての branch で commit への署名を必須にしました。

GitHub API を使えば一括で Branch Ruleset を設定できます。

https://github.com/suzuki-shunsuke/batch-task/tree/main/require-all-branches-sign

また、 Pull Request の CI で sign されているか check し、されていない場合は PR にコメントすることでコントリビューターに署名を強制しています。

https://github.com/szksh-lab-2/test-github-action/pull/305#issuecomment-2775492280

自分で「署名してください」っていうのはストレスなので CI で自動化しています。

ちなみにこの workflow では例外的に pull_request_target を使っていますが、リポジトリ中のコードは参照していませんしリスクは低いと思っています。

攻撃者は攻撃の機会を伺っていた?

For instance, there is a three-month gap between when the attackers leaked SPTBGS_MNTNR’s PAT and when they abused it.

one possible hypothesis is that the attackers monitored the projects dependent on the tj-actions/changed-files and waited for an opportunity to compromise a high-value target.

攻撃者が spotbugs のメンテナの PAT を奪取してからそれを悪用するまで 3 ヶ月空いています。
これは仮説ですが、攻撃者は tj-actions/changed-files に依存するリポジトリのリストをウォッチし、攻撃する価値の高いリポジトリが tj-actions/changed-files を使うまで機会を伺っていたのかもしれません。

参考

https://github.com/reviewdog/reviewdog/issues/2079

https://www.wiz.io/blog/new-github-action-supply-chain-attack-reviewdog-action-setup

フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link