ProBackend
vulnerability patch management
16 hours ago8 min read

Git Rebase Command Injection in Gogs Enables Server Takeover

A critical argument injection vulnerability (CVE-2026-52806) in Gogs enabled authenticated users to execute arbitrary commands by exploiting the 'Rebase before merging' function. Explore technical analysis and mitigation strategies to secure your Git infrastructure against RCE.

Klaudia Gorski

We keep making the exact same mistakes with command-line arguments. It's almost funny if the results weren't so catastrophic. Gogs, the lightweight self-hosted Git platform, has a massive zero-day vulnerability that lets anyone with a basic user account execute arbitrary code on the server. That means if your server is facing the internet, an attacker can compromise your code repositories, dump credentials, and hop onto your internal network. Tracked as CVE-2026-52806, the bug has a CVSSv4 rating of 9.4. That's a huge number. And the worst part? Gogs ships with open registration turned on by default. It's a disaster waiting to happen.

Gogs is built in Go. It's fast, tiny, and easy to set up. People love it as a GitLab alternative. But in their quest to keep things simple, Gogs has left legacy code pathways wide open. It is a potent reminder that even the most unassuming infrastructure can hold absolute risks when argument handling goes wrong.

The core of the issue lies in how Gogs manages repository merges. When a team enables the "Rebase before merging" functionality—a standard practice for cleaner pull request histories—Gogs relies on the underlying Git CLI to execute the rebase operation. The handler within the Merge() function, situated in internal/database/pull.go, fails to treat user-supplied input with the appropriate level of caution. It constructs a shell command string that directly incorporates the base branch name: git rebase --quiet <base_branch> <head_branch>. Without a mandatory end-of-options separator like -- or --end-of-options, Gogs allows Git's argument parser to interpret any branch name prefixed by double hyphens as command-line flags.

An attacker can exploit this, crafting a branch name that leverages the --exec= directive. As Git processes the rebase, it encounters this flag and executes the specified command, essentially bypassing the intended Git workflow. This isn't merely theoretical; it provides a direct line to RCE. The vulnerability highlights the persistent danger of improper shell neutralization—CWE-77, or more specifically in this case, a classic CWE-88 argument injection.

Command-Line Arguments Fail Again in Self-Hosted Gogs Platforms

Bypassing Protections: How Git Rebase Became an Execute Mechanism

The technical root cause is a study in what happens when raw input meets systemic trust. The Merge() function at the heart of this issue is a legacy component, missing the hardened git-module library abstractions that have been integrated into other, more secure areas of Gogs. While Gogs maintainers have previously patched other argument injection pathways—such as CVE-2024-39933 related to tagging and CVE-2024-39932 affecting repository diffs—the Merge() call-path remained unguarded, effectively leaving a back door open.

In a normal Git scenario, branch names are highly constrained, typically precluding the use of whitespace or special characters that could facilitate command manipulation. However, attackers are masters of finding the exceptions. On Linux systems, these limitations are circumvented by leveraging shell variable expansion, specifically ${IFS} (Internal Field Separator) as a whitespace substitute. This allowed attackers to craft branch names like --exec=touch${IFS}/tmp/rce_proof, successfully triggering the injected command.

Git branch names can legally contain $, {, }, =, and -. When an attacker creates a branch named --exec=touch${IFS}/tmp/rce_proof, and this is used as the base branch pr.BaseBranch in a PR, the rebase command becomes: git rebase --quiet '--exec=touch${IFS}/tmp/rce_proof' 'head_repo/feature'

Git's argument parser treats --exec=touch${IFS}/tmp/rce_proof as the --exec flag, not a branch name. The --exec argument runs the value via sh -c after each replayed commit, and ${IFS} expands to a space in the shell, bypassing Git's prohibition on spaces in branch names. If you need to run complex commands that contain characters forbidden in Git refs (like :, ~, ^, ?, *, [, \), you can base64-encode the payload and run it using: --exec=echo${IFS}<base64_payload>|base64${IFS}-d|sh

Windows environments required a different approach, given NTFS limitations on characters like the pipe | often used for command chaining. Since branch refs are stored as files under .git/refs/heads/<branch_name>, we can't write a base64 shell pipeline as a branch file name. Attackers here utilize a multi-stage approach, first committing a payload script file—say, .abcdef—into a repository, then executing that file via a refined rebase command: --exec=sh${IFS}.abcdef.

It gets slightly worse on Windows: MSYS2's sh, which ships with Git for Windows, often mangles shell metacharacters like $, &, and backticks. To bypass this, the exploit script runs cmd.exe //c .abcdef.bat where //c is MSYS2 escaping, natively launching a batch script containing the PowerShell payload without shell translation issues. Rapid7's Metasploit module automates these steps.

Bypassing Protections: How Git Rebase Became an Execute Mechanism

Exploit Flow and the Pull Request Status Race Condition

Under the hood, the MergeStyleRebase code path runs several Git commands in sequence: First, Gogs clones the repository: git clone -b '<malicious_branch>' <repo> <tmp_dir>. This succeeds because -b consumes the argument as the branch value, even if it looks like a sign helper. Second, Gogs adds the head remote and fetches it: git remote add head_repo <repo> plus git fetch head_repo. This succeeds normally. Third, Gogs runs target rebase: git rebase --quiet '<malicious_branch>' 'head_repo/feature'. The RCE fires here because git rebase parses the flag-like branch name and runs the shell payload. Fourth, Gogs checks out a temporary branch: git checkout -b <timestamp_branch>. Fifth, Gogs tries to check out the base branch: git checkout '<malicious_branch>'.

This fifth step is where the process breaks. Git refuses to checkout because it interprets --exec=... as a bad command flag for the checkout command. The step fails, Merge() returns a 500 error, and Gogs leaves the repository file system in a corrupted rebase state. The exploit succeeds but the repository is ruined for future merges. If the attacker created the repo themselves, they just throw it away. If they used a target's existing repo, it can only be exploited once.

But how does the PR even get to a mergeable state in the first place? If Gogs validates the pull request, shouldn't it find the malformed branch name beforehand? The answer is a race condition in testPatch(). When a PR is first created, testPatch() calls UpdateLocalCopyBranch(pr.BaseBranch). Because the local clone doesn't exist yet, it takes the Clone path, which is written with --end-of-options protection. The clone completes without issues, and Gogs marks the pull request status as PullRequestStatusMergeable. Later, Gogs runs a background routine to re-evaluate pull requests. Because the local clone now exists on the disk, it takes the Checkout path instead. Since the checkout path lacks --end-of-options protection, the checkout command crashes. Gogs catches this failure but skips updating the status checker. As a result, the PR stays 'Mergeable' in the web interface permanently. The green Merge button remains clickable, waiting for the exploit execution.

Tenant Risks and the Battle Against Open Registration Defaults

The Gogs argument injection has massive implications because of how public instances are configured. By default, open registration is active (DISABLE_REGISTRATION = false). The global repository creation limit is set to unlimited (MAX_CREATION_LIMIT = -1). An unauthenticated attacker can browse to the portal, create an account, spin up a new repository, and trigger the RCE in seconds.

There's no native sandbox or container isolate in Gogs. It executes the Git system commands as the shared git host user. This host user has complete filesystem access to all repositories hosted on the server. If an attacker compromises Gogs, they can:

  • Exfiltrate Code: Read private repositories belonging to other organizations or teams.
  • Harvest Credentials: Recover database configuration profiles, active API tokens, SSH private keys used for syncs, and 2FA credentials.
  • Pivot Internally: Use the host access to probe and compromise neighboring databases or cloud resources.
  • Manipulate Codebases: Push malicious code to upstream production branches. Because Gogs users rarely enforce commit signing, forged commits are trivial to hide.

The Gogs development team was notified of this flaw by Rapid7 on March 17. The maintainers acknowledged the report on March 28 but failed to provide a timely patch or communication updates. The security community grew anxious, particularly given how Gogs servers have been targeted before. Historically, Gogs has run into multiple argument injection struggles.

  • CVE-2024-39933: Tagging releases was vulnerable. Fixed by adding the -- separator to git tag.
  • CVE-2024-39932: Changes preview was vulnerable. Fixed by adding --end-of-options to git diff.
  • CVE-2026-26194: Release deletion option injection. Fixed by utilizing the secure git-module API.
  • CVE-2024-39930: SSH system argument injection. Fixed by appending -- to git upload-pack.

The library git-module was hardened to use --end-of-options in most calls, but the developers missed Merge() in internal/database/pull.go because it invokes the raw command runner directly.

Securing Gogs Environments Against Argument Injection

Gogs finally publicised v0.14.3 on June 7, 2026, which contains the argument injection fix. If you deploy Gogs, you must upgrade immediately. If updating isn't quick, you need to apply configuration workarounds to protect your code hosts.

First, turn off open registration in your configuration files: DISABLE_REGISTRATION = true

Second, change repository creation. You can limit it globally: MAX_CREATION_LIMIT = 0 Or you can lock it down in the admin console for individual users.

Third, adjust your repository settings to disable "Rebase before merging" options. Removing this option kills the rebase code path, shutting down the exploit vector even if you have active users with write access to target repositories.

We see Git-based vulnerabilities frequently. In fact, Gogs had an actively exploited bug, CVE-2025-8110, which forced security teams to rush patches when it was added to CISA's KEV catalog. You can see how fast federal directives demand hotfixes for active exploits, similar to the timeline in the Check Point emergency patch directive.

To maintain a secure posture, security teams should align with robust frameworks like CISA's risk-based patching approach, which highlights the growing need for rapid, prioritized remediation in the face of widespread automated exploit attempts. Self-hosted developer portals are high-value targets. Hardening them is a constant requirement.

More blogs