cat posts/%e4%bb%8a%e9%80%b1%e3%81%ae%e3%82%bb%e3%82%ad%e3%83%a5%e3%83%aa%e3%83%86%e3%82%a3%e8%80%83%e5%af%9f%ef%bc%9a%e3%83%90%e3%83%83%e3%82%b8%e3%81%af%e5%90%8c%e3%81%98%e3%81%a7%e3%82%82%e4%b8%ad%e8%ba%ab.md

今週のセキュリティ考察:署名は通るのに中身が違う——GitHub「Verified」バッジの落とし穴

署名付きの Git コミットは、署名の鍵を持たない人でも、中身(ファイル・作者・日時)を変えずに別のハッシュ値のコミットに作り直せて、GitHub はそれにも「Verified」を付ける、という研究が公開された[1][3]。

PART 1解説

3行でわかる要点

  • 何が変わるか:中身は同じで、ハッシュ値だけが変わる。署名のデータを、有効なまま別の書き方に変えられるため[1][3]。
  • 何が困るか:ハッシュ値で悪いコミットを止めるブロックリストや、重複の排除、来歴の記録が、同じ中身の「別名」をすり抜けられる[3]。
  • 何は困らないか:別のコードを紛れ込ませる手口ではない。ハッシュ値で固定(ピン留め)していれば、取得される中身は変わらない[3]。

前提:Git のコミットとハッシュ値

Git では、ファイルの変更をまとめた単位を「コミット」と呼び、コミットごとにその中身から計算したハッシュ値(長い英数字)が付きます。中身が少しでも違えば別のハッシュ値になるため、ハッシュ値は「中身の指紋」として、どのコミットかを指す名前に使われてきました。

さらにコミットに電子署名を付けると、GitHub は署名を検証して Verified と表示します[4]。CI でのバージョンの固定、問題のあるコミットの遮断、来歴の記録などは、この「ハッシュ値=中身の名前」という前提の上に成り立っています。

まず押さえる用語

用語ひとことでもっと詳しく
ハッシュ値データから計算される指紋のような値。Git はコミットをこの値で呼び分ける。—
電子署名・Verified鍵の持ち主が署名したことを確かめる仕組み。GitHub は検証できたコミットに Verified を表示する。GitHub Docs「コミット署名の検証」
署名の展性署名を、有効なまま別の形に書き換えられる性質。暗号が破られたわけではない。Bitcoin Wiki「Transaction malleability」
正規化同じ意味のデータを、決まった1つの書き方にそろえること。—

本来の仕組みと、どこが破られたか

署名付きコミットの扱いの正常時と攻撃時正常時署名付きコミット署名の形を正規化検証してVerifiedハッシュで管理攻撃時署名の形だけ書き換え署名の形を正規化Verifiedのまま別ハッシュで素通り
図1 署名の書き方を正規化しないため、同じ中身に別のハッシュ値が付く(論文と The Hacker News の記述をもとにした概念図)

Git のコミットのハッシュ値は、署名のデータも含めた中身全体から計算されます。署名には「同じ意味のまま別の書き方にできる」ものがあり、書き方を変えると、コードは1行も変わらないのにハッシュ値だけが変わります[3]。

GitHub は署名を検証する前に書き方を1つにそろえない(正規化しない)ため、書き換えた側にも Verified が付きます[3]。図1の赤い破線が、行われていない「正規化」です。研究者は、ECDSA・RSA・EdDSA・S/MIME のいずれの署名でも再現するツールを公開しています[1][2][3]。

攻撃の流れ

sequenceDiagram
    autonumber
    participant C as 正規の作者
    participant G as GitHub
    participant T as 第三者
    C->>G: 署名付きコミットをpush
    G-->>C: Verified
    T->>T: 署名を有効なまま書き換え(中身は同じ)
    T->>G: 別のハッシュ値のコミットをpush
    G-->>T: こちらもVerified
    Note over G: ハッシュ値のブロックリストは別名を知らない
  • 署名の鍵を失効させても、GitHub は一度付けた Verified を検証し直さないため、表示が残る[3]。
  • 研究者は1月に Git、3月に GitHub へ報告したが、論文の公開時点では対応されていなかった[3]。

「ハッシュ値は一意の指紋」という思い込み(考察)

  • 原因は鍵の漏洩ではなく、検証の設計にある。崩れているのは「ハッシュ値は中身の一意な名前だ」という前提のほう。
  • ハッシュの衝突ではない。1つのコミットを、有効なまま何通りにも書けるということ[3]。
  • 乗っ取られたミラー(複製サーバー)が、正規とは違うハッシュ値の、有効な署名付きコミットを配ることもできる。受け取る側は、どれが「本来のハッシュ値」かを署名だけでは判断できない[3]。
  • Bitcoin も同じ ECDSA の性質に悩まされ、署名の形を1つに限ることで対処した。署名を信頼する前に正規化する、という昔からの教訓[3][5]。

いま確認すること

  • 【全員】ハッシュ値でコミットをブロック、重複排除、来歴管理している仕組みがないか確かめる。あれば、同じ中身の別名ですり抜けられる[3]。
  • 【全員】Actions などをコミットのハッシュ値で固定する運用は引き続き有効。取得される中身は変わらない[3]。
  • 【全員】Verified を「誰が署名したかの確認」として扱い、「このハッシュ値だけが正しい」という意味には使わない。

出典・参考リンク

一次情報

  1. Ginesin, arXiv:2607.02820
  2. git-chain-malleator(研究者の公開ツール)

報道・解説

  1. The Hacker News — GitHub Verified Commits Can Be Rewritten Into New Hashes

用語解説

  1. GitHub Docs — About commit signature verification
  2. Bitcoin Wiki — Transaction malleability

PART 2問題(情報処理安全確保支援士 試験形式)

問1 午前II形式(4択)

署名付き Git コミットについて今回の研究で示された問題として、最も適切なものはどれか。

  • ア 署名の鍵がなくても、別のコードを含むコミットに Verified を付けられる
  • イ 中身が同じまま、別のハッシュ値をもつ Verified のコミットを作れる
  • ウ SHA-1 の衝突によって、異なる2つのコミットが同じハッシュ値になる
  • エ 署名の鍵を失効させると、過去の Verified 表示がすべて消える
解答・解説を表示

解答:イ

署名を有効なまま書き換えることで、ファイル・作者・日時が同じでハッシュ値だけが違うコミットを作れる。アは研究がはっきり否定している(中身は変わらない)。ウはハッシュの衝突ではない。エは逆で、失効後も Verified が残る。

↑ Part 1「本来の仕組みと、どこが破られたか」を読み直す

問2 午後形式(記述)

A社では、問題が見つかったコミットをハッシュ値のブロックリストに登録し、CI で取り込まないようにしている。GitHub の Verified コミットに関する今回の研究を受けて、B さんは①この仕組みだけでは不十分だと指摘した。

設問 本文中の下線①について、B さんがそう指摘した理由を40字以内で述べよ。

解答・解説を表示

解答例:同じ中身を署名ごと書き換え、リストにない別のハッシュ値で再び送り込めるから(37字)

署名を有効なまま書き換えると、中身は同じで別のハッシュ値になり、Verified も付く。ハッシュ値だけで止める仕組みは、この別名を知らない。「秘密鍵が漏れている可能性があるから」は、今回の原因(署名の書き方の自由度)と異なる。

↑ Part 1「いま確認すること」を読み直す