Gogs Authenticated RCE via git rebase --exec Argument Injection (Unpatched)

Gogs Authenticated RCE via git rebase –exec Argument Injection (Unpatched)

Original text: “Critical Gogs RCE Vulnerability Lets Any Authenticated User Execute Arbitrary Code” — The Hacker News (May 28, 2026). The deep technical content is drawn from the canonical Rapid7 advisory by Jonah Burgess at rapid7.com. Code snippets and the disclosure timeline below are reproduced verbatim with attribution.

Executive Summary

Rapid7’s Jonah Burgess has disclosed an unpatched authenticated RCE in Gogs, the popular self-hosted Go-based Git server, rated CVSS 9.4. The vulnerability is a clean, classic argument-injection bug: when a pull request is merged with the “Rebase before merging” strategy, Gogs builds and runs a shell command line of the form git rebase --quiet <base> <head> without a -- end-of-options separator. The <head> token comes directly from the branch name of the attacker’s pull request. If the attacker names their branch --exec=touch${IFS}/tmp/rce_proof, Git parses that token as the --exec flag of git rebase, and runs the supplied shell command after every replayed commit — on the Gogs server, in the Gogs process’s context.

The exploit chain is unusually low-friction. On a default-configured instance any user who can register can open the attack: create an account, create a repository (the user is automatically its owner), flip the “Rebase merging” toggle in repo settings, open a PR with the malicious branch name, and merge. No admin role, no other user’s interaction, no CVE number to track. Rapid7 reported the bug on 17 March 2026; the maintainer acknowledged on 28 March and then went silent through April, May 6, and May 20 reminders. The patch is still not shipped. Rapid7 disclosed publicly on 28 May with a Metasploit auxiliary module that ships with the advisory. Around 1,141 Gogs instances are visible on the public internet; many more sit behind VPNs. Until a fix lands, the only mitigations are the two configuration switches in app.iniDISABLE_REGISTRATION = true and MAX_CREATION_LIMIT = 0 — plus auditing every repository’s “Rebase merging” setting.

Gogs critical RCE via git rebase --exec argument injection in Rebase before merging
Hero image — The Hacker News coverage of the unpatched Gogs argument-injection RCE. Source: original article.

At a Glance

FieldValue
Vendor / productGogs (self-hosted Git service, written in Go)
Bug classArgument injection → OS command execution via git rebase --exec
CVENone assigned
Advisory IDsGHSA-qf6p-p7ww-cwr9 (private security advisory); Rapid7 blog
CVSSv4 base score9.4 (Critical)
CVSSv4 vectorAV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Affected versions0.14.2 confirmed; 0.15.0+dev (commit b53d3162) confirmed; all prior versions supporting “Rebase before merging” likely vulnerable
Affected OSesLinux, Windows, macOS
Internet exposure~1,141 internet-facing instances (Rapid7 census)
Privileges requiredAny authenticated user (default registration is enabled); fallback — write/merge access to a repo with rebase merging on
User interactionNone
DiscovererJonah Burgess, Senior Security Researcher, Rapid7 Labs (handle CryptoCat)
Reported to maintainer17 March 2026 (GHSA private advisory)
Patch shippedNo (as of 28 May 2026 disclosure)
Public exploitMetasploit auxiliary module: rapid7/metasploit-framework PR #21515
Summary of the Gogs argument-injection RCE. Source: Rapid7 advisory + The Hacker News.

The Bug in One Function

The defect sits in the Merge() function in internal/database/pull.go. When the “Rebase before merging” strategy is selected, Gogs invokes git from Go like this:

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

Both pr.BaseBranch and remoteHeadBranch are user-controlled strings derived from the pull request. There is no -- end-of-options separator between the flags and the positional arguments, so Git’s argument parser is willing to interpret a branch-name-shaped string as a flag. The relevant Git flag is git rebase --exec=<cmd>, which runs <cmd> after every replayed commit — the very flag that turns rebase into a one-shot command executor.

Git itself prohibits a space character in a ref name, which would normally make the attack hard — you can’t name a branch --exec=touch /tmp/rce. The trick is ${IFS}, the shell’s Internal Field Separator variable: ${IFS} contains spaces by default, so any shell that re-tokenises the string (Gogs eventually does, via process.ExecDir’s child-process plumbing) treats ${IFS} as whitespace. The canonical PoC branch name is therefore:

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

And the actual command-line Gogs executes — reproduced verbatim from the Rapid7 advisory — is:

git rebase --quiet '--exec=touch${IFS}/tmp/rce_proof' 'head_repo/feature'

For payloads that need characters Git also forbids in ref names (:, ~, ^, ?, *, [, , //), the canonical workaround is base64:

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

Exploit Chain

Default Mode — Attacker Owns a Repository

On a default-configured Gogs instance (registration enabled, no repository creation limit), the chain is:

  • Register an account.
  • Create a repository — the registering user is automatically its owner.
  • Toggle “Rebase merging” on in repository settings.
  • Push a branch whose name is --exec=<command> (the form above).
  • Open a pull request from that branch into the repo’s default branch.
  • Click “Merge”. Gogs runs the rebase. The --exec flag fires the attacker’s command. RCE.

No other user’s interaction is required at any point. The attacker is the author of the PR and the owner of the target repository — they can also be the approver and the merger of their own pull request.

Fallback Mode — Hardened Instance, Existing Repo

On an instance where registration or repository creation is restricted, the attacker needs write access to any repository that has rebase merging enabled. That’s a much lower bar than admin: a normal developer on the team is enough. Push the malicious branch, open a PR, hit merge.

Impact

  • The command runs as the Gogs server process. On most installations that means the git user under which Gogs is configured to run — with read access to every repository on the instance.
  • Cross-tenant data breach. A multi-tenant Gogs instance is fully compromised by any single tenant user: the attacker can read every other tenant’s private repos under the shared REPOSITORY_ROOT.
  • Credential dumping: the Gogs configuration, database credentials, and any cached Git credentials are accessible. SSH keys uploaded to the server are reachable.
  • Repository tampering: the attacker can rewrite history on any hosted repo, plant supply-chain backdoors, or quietly add their own SSH keys to victim accounts.
  • Lateral movement: anything the Gogs host can reach — CI runners, internal Docker registries, the corporate intranet — is now within scope from the implant.

Forensics & Detection

One of the more useful artefacts of the advisory is the forensic side: the exploit succeeds before the merge errors out. Git’s rebase runs the --exec command after each replayed commit; only then does the rebase logic ultimately fail because it cannot complete the merge cleanly, and Gogs returns an HTTP 500 to the attacker. By the time the 500 is logged the implant has already run.

The Rapid7 advisory shows the exact error-log line that gets written:

[E] ...merge: git checkout '--exec=<...>': exit status 128 - error: unknown option `exec=<...>'

The two operational modes have very different forensic footprints:

  • Default mode (attacker creates and deletes their own repository): once the repo is deleted, the only trace is the HTTP 500 line in the Gogs server log and whatever the Metasploit module’s API-token cleanup left behind — Rapid7 notes that Gogs “does not expose a token deletion API endpoint”, so a token named msf_<hex> may persist on the attacker’s account.
  • Existing-repository mode: the malicious branch name persists in the repository history. Git refs containing the substring --exec= on any branch are diagnostic. Any Git event source pushing data into a SIEM should alert on that pattern.

High-Fidelity Hunts

  • SIEM rule on the Gogs error log: alert on exit status 128 + unknown option `exec= in the merge error pattern shown above.
  • Filesystem hunt: on the Gogs host, recursively grep repository pack files / refs for any ref name containing --exec=. The pattern is essentially never legitimate in a real Git ref.
  • Process telemetry: the Gogs service spawning a child shell process is the smoking gun. parent=gogs + child=/bin/sh|/bin/bash + grandchild ≠ git is a high-fidelity rule.
  • API-token audit: any user with an API token starting with msf_ is, by definition, the Metasploit module’s leftover. That string is the literal token-name prefix used by the module.

Mitigations (No Patch Available)

Rapid7’s recommended mitigations — verbatim from the disclosure — are configuration-level:

# app.ini
DISABLE_REGISTRATION = true       # prevent untrusted users from creating accounts
MAX_CREATION_LIMIT = 0            # prevent users from creating their own repositories

Plus a third audit step: turn off “Rebase merging” on every repository that has it enabled today, or restrict write/merge access to that subset of repositories to a known-trusted user set.

Neither configuration switch removes the underlying argument-injection sink — they reduce the attacker pool. The fundamental fix has to be a Gogs code change adding -- between the flags and the positional arguments in the git rebase invocation (and in any other internal git invocation that takes user-controlled ref names, of which there are likely more once the auditor goes looking).

Disclosure Timeline

DateEvent
2026-03-16Vulnerability discovered (Rapid7 Labs).
2026-03-17Reported to maintainer via GitHub Security Advisory GHSA-qf6p-p7ww-cwr9.
2026-03-28Maintainer acknowledges receipt.
2026-04-21Rapid7 requests a status update. No response.
2026-05-06Disclosure-extension offer sent. No response.
2026-05-20Final disclosure date notified to maintainer.
2026-05-28Public disclosure (Rapid7 advisory, The Hacker News coverage, Metasploit module).
Disclosure timeline. Source: Rapid7 advisory.

Why This Bug Class Recurs

Argument injection in Git wrappers is a recurring family of bugs. Anywhere a long-lived service shells out to git, tar, rsync, curl, find, ssh, etc., with one or more positional arguments coming from untrusted callers, the same defect can apply if the developer forgot the -- separator. Git is particularly bad to hand untrusted ref names to because both sides of the injection are powerful — rebase --exec, clone --upload-pack, archive --remote, config --add, and more are all flag-injection RCE primitives on their own.

Modern Go and Rust HTTP servers calling out to git via os/exec are particularly susceptible because both languages encourage variadic Exec(name, ...args) patterns that lull the developer into thinking the arguments are positional. They are positional for the host language, but Git’s own argument parser still treats anything starting with -- as a flag.

Key Takeaways

  • Gogs has a critical authenticated RCE (CVSS 9.4) reachable from any user account on a default-configured instance via git rebase --exec argument injection. No patch is shipped.
  • The attack is a single PR with a branch named --exec=<cmd>; ${IFS} stands in for spaces because Git rejects spaces in ref names. Base64 plus shell-pipe handles characters Git also forbids.
  • No CVE was assigned. The advisory ID to track is the GitHub Security Advisory GHSA-qf6p-p7ww-cwr9 and Rapid7’s blog post.
  • Roughly 1,141 Gogs instances are visible on the public internet; many more sit behind VPNs. The same exploit works from inside a VPN.
  • Successful exploitation gives the attacker code execution as the Gogs service user, which on virtually every install means read access to every other tenant’s repositories — cross-tenant data breach is the default outcome, not a stretch consequence.
  • The forensic footprint of the default attack mode is one HTTP 500 in the server log plus an msf_<hex> API token; the existing-repo mode leaves the malicious ref name on the repository.
  • The root cause is one missing -- in an os/exec call — a recurring family of bug across any service that shells out to git.

Hardening Checklist

  • Set DISABLE_REGISTRATION = true and MAX_CREATION_LIMIT = 0 in app.ini on every Gogs instance you own, today. Even on internal-only instances. This is the only configuration-side reduction available.
  • Audit every repository’s “Rebase merging” setting. Turn it off on all repositories where it is currently enabled. The merge strategy can be enabled per-repo by the repo owner; you need to make it off-by-policy.
  • Grep Git refs and pack files for the substring --exec= on every repository on the host. Any match is an IoC.
  • Alert on the Gogs error log for the merge-exec patternexit status 128 + unknown option \`exec= — SIEM rule.
  • Process-telemetry rule: parent=gogs + child=/bin/sh|/bin/bash + grandchild ≠ git is high-fidelity.
  • Audit API tokens: any user with a token named msf_* is, by definition, the Metasploit auxiliary module’s artefact. Delete the user/token; treat as a confirmed exploit attempt.
  • Plan for migration. The Gogs project’s response cadence on this advisory (acknowledged then silent for two months) is itself a risk indicator. Gitea is the live-fork most teams migrate to; Forgejo is the community fork that emphasises explicit governance.
  • Treat “authenticated” as “internet-reachable” if your Gogs instance is exposed via SSO — SSO does not narrow the attacker pool, it just changes the registration prerequisite.

Conclusion

This is a textbook unfixed argument-injection bug exposed by an unusually responsive disclosure process — Rapid7 reported on March 17, the maintainer acknowledged on March 28, and silence followed. Public disclosure on May 28 plus a shipping Metasploit module mean the risk is now operational, not theoretical. The bug class is the same one defenders have seen against forgejo-style Git servers for years: any place a long-lived process shells out to git with user-controlled ref names without a -- separator is a candidate. Until Gogs ships the one-line fix, the answer for operators is a combination of configuration lockdown, monitoring against the very specific log line and ref-name pattern in the advisory, and quietly planning the migration path. Credit and thanks to Jonah Burgess and the Rapid7 Labs team for the disclosure, the detailed write-up, and the Metasploit module that ships with it.

References

Original text: “Critical Gogs RCE Vulnerability Lets Any Authenticated User Execute Arbitrary Code” at The Hacker News (May 28, 2026). Deep technical detail — CVSS vector, vulnerable function, branch-name PoC, disclosure timeline — from the canonical Rapid7 advisory by Jonah Burgess.

Comments are closed.