Blog
GiteaCVE-2026-60004CISA KEVGit Hook RCE

Gitea CVE-2026-60004: Self-Register, Push a Patch, Own the Git Server

Gitea CVE-2026-60004 is a CVSS 9.8 RCE in the diffpatch endpoint. Open registration turns a self-hosted Git server into a pre-auth shell. CISA KEV, exploited now.

Zero Hunt Research··8 min read

The fastest path to remote code execution on 8,393 internet-facing Gitea servers this week was the sign-up button. CVE-2026-60004 — CVSS 9.8, added to CISA's Known Exploited Vulnerabilities catalog on August 25 with a federal patch deadline of August 28 — turns Gitea's diffpatch endpoint into a pre-auth shell. Not because the endpoint is exposed to anonymous users, but because Gitea ships with open registration on by default, so an attacker registers an account, creates a repository, and now holds exactly the write access the exploit needs. The gap between "unauthenticated visitor" and "code execution as the git service account" is three HTTP requests.

This is the failure mode self-hosted source control keeps producing: the SCM server is treated as internal infrastructure, so it's deployed with defaults that assume trusted users, and then it ends up reachable from the internet anyway. Gitea is the small, fast, self-hosted alternative to GitHub Enterprise and GitLab — which is precisely why it lands on a VPS with a public IP and never gets a second look.

How CVE-2026-60004 turns a patch into a Git hook

Gitea's web UI lets a user edit a file and generate a patch, then apply it — the diffpatch workflow. The vulnerability, disclosed and patched in late July and credited to researcher Shai Rod (NightRang3r), is that specially constructed patch content causes Gitea to write an executable Git hook into a temporary repository and then invoke it when Git updates the index.

Git hooks are shell scripts Git runs at defined points in its lifecycle — pre-receive, post-receive, update, post-commit. They are executables sitting in a hooks/ directory, and Git runs them with the privileges of whoever runs the Git operation. On a Gitea server, that's the Gitea service account — the git user, frequently inside the application container. The full mechanism: submit the malicious patch twice, and the second submission writes an attacker-controlled executable into the hook directory and triggers it. Arbitrary shell, no memory-corruption gymnastics, no ROP chain — just Git doing exactly what Git hooks are designed to do, pointed at a file the attacker planted.

The privilege math is what makes this a 9.8 rather than a 7-something:

  • Write access to a repository is the only requirement. Any repo the account can push to works — including one the attacker just created.
  • Open registration is the default. Gitea's out-of-box config lets anyone create an account. So the "authenticated write access" precondition collapses into "can reach the login page."
  • The service account is not sandboxed by default. The git user typically has a real shell and can reach the network, the repository storage, and any secrets mounted into the container.

"Our Gitea box is internal-only, behind the VPN." It answered on a public IP because a developer opened port 3000 to demo a repo, and open registration was never disabled. Shadowserver counted 8,393 servers making the same assumption on August 27.

Eleven seconds from scan to miner

This is not theoretical. A developer reported their outdated Gitea instance was compromised through automated scanning, documented by The Hacker News and Help Net Security. The attacker registered an account, created a repository, and executed code inside the Gitea container as the git user. The payload was a cryptocurrency-miner-like dropper: it ran system reconnaissance, killed competing miner processes, fetched an architecture-specific binary, executed it, and deleted itself. The active on-host phase lasted roughly eleven seconds. The only reason the victim noticed at all was sustained CPU above 70%, which tripped the hosting provider's alerting.

Eleven seconds is the number defenders should sit with. There is no window for a human analyst to triage a diffpatch request, no time for a signature update to propagate, no morning-SIEM-digest moment. The mining payload here was almost a courtesy — it announced itself with CPU. The same primitive drops a reverse shell, exfiltrates every repository, and rotates its own persistence hook without ever touching the CPU graph. A miner is the loudest thing an attacker with unauthenticated RCE on your source control can choose to do.

And source control is the worst place to lose. A Gitea server holds proprietary code, CI/CD tokens, deploy keys, webhook secrets, and signing keys. RCE there is not a single-host incident — it is a supply-chain foothold with a signed path into everything the pipeline can reach.

Why scanners miss the actual attack path

Plenty of tools will flag "Gitea version 1.26 — vulnerable to CVE-2026-60004." That's the easy 20% of the finding. The part that decides whether you're actually exploitable is a chain a version banner can't answer:

  1. Is the server reachable from an untrusted network?
  2. Is open registration enabled, or is the account boundary actually enforced?
  3. Can a freshly registered account create a repository (the write-access precondition)?
  4. Does the diffpatch path succeed end to end and land an executable in a hook directory?
  5. What does the git service account reach once code runs — secrets, network, other repos?

A CVSS number and a version string give you step 1. Steps 2 through 5 are the difference between "theoretically vulnerable" and "an attacker is mining Monero in your container right now." Most scanning stops at the banner because reasoning through a multi-step exploit chain — register, create, submit twice, land the hook — is exactly what signature matching cannot do.

Remediation

Treat any internet-reachable Gitea below 1.27.1 as compromised until proven otherwise. A public proof-of-concept exists and mass scanning is live.

1. Am I affected?

Check the running version and your exposure:

# Version — anything from 1.17 through 1.27.0 is vulnerable
gitea --version
# or from the container
docker exec <gitea-container> gitea --version

# Is open registration on? (the pre-auth multiplier)
grep -A2 '\[service\]' /etc/gitea/app.ini | grep -i DISABLE_REGISTRATION
# absent or =false means anyone can self-register

# Is it reachable from untrusted networks? Check what's bound to :3000 / :443
ss -tlnp | grep -E ':3000|:443'

Affected range: 1.17 through 1.27.0. If it answers on a public interface with registration enabled, assume active exploitation pressure.

2. Patch — exact fixed version

Upgrade to Gitea 1.27.1 (released July 27, 2026) or later; current maintenance is 1.27.2. This is the only complete fix — the compensating controls below narrow the window, they do not close it.

3. Can't patch this hour? Compensating controls

  • Disable open registration: set [service] DISABLE_REGISTRATION = true in app.ini and restart. This removes the unauthenticated path — attackers now need real credentials.
  • Block the endpoint at the proxy: deny POST requests whose path contains /diffpatch at your reverse proxy / WAF.
  • Sandbox the service account: systemd NoNewPrivileges=true, PrivateTmp=true, ProtectSystem=strict; give the git account /usr/sbin/nologin.
  • Get it off the public internet: put the instance behind VPN or an identity-aware proxy. An internal-only SCM should be internal.

4. Hunt for compromise (MITRE ATT&CK)

The exploit is POST to diffpatch (T1190) → shell via a planted hook (T1059) → hook persistence (T1505.003 / T1547) → resource hijacking (T1496).

  • Rogue hooks — inspect every repository's hook directory for recently created or unexpected executables:
# adjust to your repo root: /var/lib/gitea, /data/git, /home/git/gitea-repositories, /opt/gitea
find /var/lib/gitea/repositories -path '*/hooks/*' -type f -perm -u+x -mtime -14 -ls

Legitimate Gitea hooks are managed and predictable; an attacker-planted pre-receive/post-receive/update with recent mtime is the signal.

  • Process lineage — the Gitea service spawning sh, bash, curl, wget, nc, or a miner binary is a live-execution IOC.
  • Access logsPOST to /diffpatch paths from freshly-registered accounts or non-developer source IPs. The signature that ties the chain together: a POST /diffpatch whose source IP also hit /user/sign_up minutes earlier.
  • The attacker's account — because the entry point is self-registration, the intruder left a row in your user table. Hunt for accounts created in the exposure window and the tokens or SSH keys they added for persistence:
# SQLite backend (adapt for Postgres/MySQL); GITEA_HOME e.g. /var/lib/gitea
sqlite3 "$GITEA_HOME/data/gitea.db" \
  "SELECT id,name,lower_email,datetime(created_unix,'unixepoch') FROM user ORDER BY created_unix DESC LIMIT 20;"
# Tokens / SSH keys added by suspect accounts (persistence)
sqlite3 "$GITEA_HOME/data/gitea.db" \
  "SELECT t.name,u.name,datetime(t.created_unix,'unixepoch') FROM token t JOIN user u ON t.uid=u.id ORDER BY t.created_unix DESC LIMIT 20;"
  • Resource signal — the observed payload announced itself as sustained >70% CPU. Don't rely on it — the primitive is silent by default.

5. Eradicate and verify

  1. Preserve first: copy access logs (14+ days), systemd journals, container logs, and any rogue hooks for forensics before you touch them.
  2. Remove implants: delete planted hooks, kill and remove dropped binaries, check for cron/systemd persistence the shell may have added.
  3. Rotate everything the box could read: user and CI/CD tokens, deploy keys, OAuth apps, webhook secrets, and code-signing keys. Assume source code and pipeline secrets are exposed.
  4. Verify after patching: confirm 1.27.1+, confirm registration disabled or bounded, re-run the hook and process hunts, and only then reconnect to untrusted networks.

Where continuous validation changes the outcome

CVE-2026-60004 is a textbook case for treating exposure as something you prove, not something you assume. The vulnerability existed in Gitea from 1.17 onward; what made a given server exploitable was a chain of configuration state — public reachability, open registration, repo-creation rights, an unsandboxed service account — that no version scan resolves. That chain is what Zero Hunt's AI Generative Pentest pillar is built to walk.

The 10-agent swarm doesn't stop at a version banner. Its Recon agent finds the exposed Gitea instance, the Web and Credential agents test whether open registration actually lets a fresh account reach repository write access, and the Exploit agent generates a per-target diffpatch chain — code written locally for your instance, not pulled from a public PoC — to confirm whether a hook actually lands and what the service account can reach. Every step is validated in the AI Gym against the Vulhub-Bench CVE corpus before it runs in production, and each finding is ECDSA-signed so the "we confirmed this was exploitable on this date" evidence holds up in an audit or an insurance claim. Because campaigns are change-triggered, the day a developer opens port 3000 to demo a repo, a full validation fires within the hour — instead of surfacing in next year's pentest, after the miner.

And for the eleven-second on-host phase, AI Traffic Analysis is the backstop the CPU graph isn't: the payload fetch of an architecture-specific binary and the outbound connection to a mining pool or C2 are behavioral egress signals a deep-learning model trained on billions of PCAP sequences flags while the activity is happening — not in the morning digest, after the eleven seconds are long gone.

Patching to 1.27.1 closes this specific door. Knowing which of your servers an attacker could actually walk through — before they do — is the discipline that closes the next one.