# Gogs Security

{{#include ../../banners/hacktricks-training.md}}

## What is Gogs

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

## Git option injection through refs / branch names

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.

Typical dangerous pattern:

```bash
git <subcommand> <user-controlled-ref>
```

Safer pattern expected in defensive code:

```bash
git <subcommand> -- <user-controlled-ref>
# or
git <subcommand> --end-of-options <user-controlled-ref>
```

A common false assumption is that validating the ref with `git rev-parse --verify <ref>` is enough. It is **not**:

- the attacker can first **create a real branch** whose name starts with `--`
- `rev-parse --verify` only checks that the ref resolves to an object
- a later unsafe Git invocation may still parse the same value as an **option**

This turns any Git-hosting feature that reuses stored branch names into a potential RCE primitive.

## Abusing `git rebase --exec` for RCE

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

```bash
git rebase --quiet <baseBranch> <headBranch>
```

and `<baseBranch>` is attacker-controlled, a branch such as:

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

can be interpreted as a **Git flag** instead of a branch name.

### Why `${IFS}` matters

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:

```bash
--exec=touch${IFS}/tmp/rce_proof
--exec=id${IFS}>/tmp/out
```

For payloads requiring Git-forbidden characters (`:`, `~`, `^`, `?`, `*`, `[`, `\\`, `//`), encode the real command and decode it at execution time:

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

## Windows-specific payload delivery

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:

1. Commit a payload script into the repository (for example `.abcdef`)
2. Create a branch like:

```bash
--exec=sh${IFS}.abcdef
```

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:

```bash
cmd.exe //c .abcdef.bat
```

where `//c` is the MSYS2-safe form of Windows `/c`.

## Merge / PR state-machine abuse

When testing Git-hosting platforms, do not only review the final dangerous command. Also review **earlier validation paths** and **background rechecks**.

A useful exploitation pattern is:

1. Initial validation path uses a **safe** clone/fetch flow with `--end-of-options`, so the malicious branch is accepted as data
2. The pull request becomes **mergeable**
3. A later merge or checkout path reuses the stored branch name in an **unsafe** Git call
4. Code execution happens even if a later step fails and the UI returns **HTTP 500**

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.

## Practical hunting ideas

When reviewing a Gogs instance or similar Git service, check for:

- Branch names beginning with `--`
- Merge failures involving `git checkout '--exec=...'`
- Pull requests stuck as mergeable even though later branch validation fails
- Repositories left in partial rebase / broken Git state after failed merges
- Unexpected committed helper files on Windows payload paths (for example dotfiles plus `.bat` launchers)
- Suspicious API tokens created shortly before failed PR merges

Example log artifact:

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

## References

- [Rapid7 - Authenticated RCE via Argument Injection in Gogs (NOT FIXED)](https://www.rapid7.com/blog/post/ve-authenticated-rce-via-argument-injection-gogs-unfixed)
- [Metasploit module PR for Gogs rebase argument injection](https://github.com/rapid7/metasploit-framework/pull/21515)
- [Git rebase documentation (`--exec`)](https://git-scm.com/docs/git-rebase)

{{#include ../../banners/hacktricks-training.md}}
