Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [Basic Github Information](pentesting-ci-cd/github-security/basic-github-information.md)
- [Gitea Security](pentesting-ci-cd/gitea-security/README.md)
- [Basic Gitea Information](pentesting-ci-cd/gitea-security/basic-gitea-information.md)
- [Gogs Security](pentesting-ci-cd/gogs-security/README.md)
- [Concourse Security](pentesting-ci-cd/concourse-security/README.md)
- [Concourse Architecture](pentesting-ci-cd/concourse-security/concourse-architecture.md)
- [Concourse Lab Creation](pentesting-ci-cd/concourse-security/concourse-lab-creation.md)
Expand Down
121 changes: 121 additions & 0 deletions src/pentesting-ci-cd/gogs-security/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# 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}}