Skip to content

Commit 20f3588

Browse files
committed
core.hooksPath: add some protection while cloning
Quite frequently, when vulnerabilities were found in Git's (quite complex) clone machinery, a relatively common way to escalate the severity was to trick Git into running a hook which is actually a script that has just been laid on disk as part of that clone. This constitutes a Remote Code Execution vulnerability, the highest severity observed in Git's vulnerabilities so far. Some previously-fixed vulnerabilities allowed malicious repositories to be crafted such that Git would check out files not in the worktree, but in, say, a submodule's `<git>/hooks/` directory. A vulnerability that "merely" allows to modify the Git config would allow a related attack vector, to manipulate Git into looking in the worktree for hooks, e.g. redirecting the location where Git looks for hooks, via setting `core.hooksPath` (which would be classified as CWE-427: Uncontrolled Search Path Element and CWE-114: Process Control, for more details see https://cwe.mitre.org/data/definitions/427.html and https://cwe.mitre.org/data/definitions/114.html). To prevent that attack vector, let's error out and complain loudly if an active `core.hooksPath` configuration is seen in the repository-local Git config during a `git clone`. There is one caveat: This changes Git's behavior in a slightly backwards-incompatible manner. While it is probably a rare scenario (if it exists at all) to configure `core.hooksPath` via a config in the Git templates, it _is_ conceivable that some valid setup requires this to work. In the hopefully very unlikely case that a user runs into this, there is an escape hatch: set the `GIT_CLONE_PROTECTION_ACTIVE=false` environment variable. Obviously, this should be done only with utmost caution. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 4412a04 commit 20f3588

File tree

2 files changed

+27
-1
lines changed

2 files changed

+27
-1
lines changed

config.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1525,8 +1525,19 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
15251525
if (!strcmp(var, "core.attributesfile"))
15261526
return git_config_pathname(&git_attributes_file, var, value);
15271527

1528-
if (!strcmp(var, "core.hookspath"))
1528+
if (!strcmp(var, "core.hookspath")) {
1529+
if (current_config_scope() == CONFIG_SCOPE_LOCAL &&
1530+
git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0))
1531+
die(_("active `core.hooksPath` found in the local "
1532+
"repository config:\n\t%s\nFor security "
1533+
"reasons, this is disallowed by default.\nIf "
1534+
"this is intentional and the hook should "
1535+
"actually be run, please\nrun the command "
1536+
"again with "
1537+
"`GIT_CLONE_PROTECTION_ACTIVE=false`"),
1538+
value);
15291539
return git_config_pathname(&git_hooks_path, var, value);
1540+
}
15301541

15311542
if (!strcmp(var, "core.bare")) {
15321543
is_bare_repository_cfg = git_config_bool(var, value);

t/t1800-hook.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,19 @@ test_expect_success 'git hook run a hook with a bad shebang' '
177177
test_cmp expect actual
178178
'
179179

180+
test_expect_success 'clone protections' '
181+
test_config core.hooksPath "$(pwd)/my-hooks" &&
182+
mkdir -p my-hooks &&
183+
write_script my-hooks/test-hook <<-\EOF &&
184+
echo Hook ran $1
185+
EOF
186+
187+
git hook run test-hook 2>err &&
188+
grep "Hook ran" err &&
189+
test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \
190+
git hook run test-hook 2>err &&
191+
grep "active .core.hooksPath" err &&
192+
! grep "Hook ran" err
193+
'
194+
180195
test_done

0 commit comments

Comments
 (0)