RCE via git hooks
A git hook is a script that git runs automatically around certain actions (commit, push, merge, checkout...). They live in .git/hooks/ inside each repository and are local to that repo: they aren't versioned and don't travel with a clone, which is why they're easy to overlook. If the script is executable, git runs it; otherwise it's ignored.
The most useful ones for escalation run before the action completes:
pre-commit: right before a commit is recorded.prepare-commit-msg/post-commit: around the message and after the commit.pre-push,post-checkout,post-merge: in their respective operations.
The escalation pattern. The hook runs with the privileges of the user launching the git operation, not whoever wrote the hook. The dangerous combination appears when:
- A privileged process (a
rootcron, a service) periodically runs agit commit,git pullor similar over a repository. - We can write to that repository's
.git/hooks/directory (because we own the working tree, e.g. a repo in our$HOME).
Then planting the right hook executes code as that privileged process:
echo '#!/bin/bash' > /path/to/repo/.git/hooks/pre-commit
echo 'chmod u+s /bin/bash' >> /path/to/repo/.git/hooks/pre-commit
chmod +x /path/to/repo/.git/hooks/pre-commitOn the process's next cycle, root's git commit fires the pre-commit and /bin/bash gets the SUID bit. Recovering root is immediate by keeping the EUID:
bash -p # -p doesn't drop the SUID binary's EUID 0
id # euid=0(root)Instead of SUID you can use any payload: a reverse shell, copying a key into /root/.ssh/authorized_keys, adding a user, etc. SUID on bash is the cleanest and most reliable.
How to spot it. The clue is usually a recurring process. Without pspy a loop diffing ps output works:
old=$(ps -eo command); while true; do new=$(ps -eo command); diff <(echo "$old") <(echo "$new"); old=$new; doneSeeing a recurring git commit/git-sync launched by root over a writable repo is the signal. It fits the general cron over a writable resource pattern: the abusable part isn't the cron itself, but that it touches a resource under our control.
Defence. Don't run privileged git operations over repositories whose working tree belongs to an unprivileged user; use core.hooksPath pointing to a read-only, controlled directory; or git -c core.hooksPath=/dev/null to disable hooks in automation.
Machines where it's used
- CompiledHackTheBox
- OpenSourceHackTheBox