← Back to Feed CACHED · 2026-08-12 14:58:41 · CACHE_KEY CVE-2026-59765
CVE-2026-59765 · CWE-918 · Disclosed 2026-07-21

Gitea: SSRF via Migration Asset Downloads Bypasses hostmatcher — Reads Internal Files and Cloud Metadata

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

Your forge's import wizard is a free proxy to your cloud metadata endpoint and config files

CVE-2026-59765 is a server-side request forgery in Gitea's repository migration feature affecting all versions before 1.27.0. Gitea enforces SSRF protections via hostmatcher.NewDialContext() on webhook and clone URLs, validating resolved IPs at the TCP dial level. However, three code paths — in modules/uri/uri.go, services/migrations/gitea_uploader.go, and services/migrations/dump.go — use Go's bare http.Get() / http.DefaultClient, completely bypassing the hostmatcher. An attacker with migration privileges (admin or org owner) can craft release assets or PR patch URLs pointing to file:///etc/gitea/app.ini (exposing DB credentials, JWT signing secrets, SMTP passwords) or http://169.254.169.254/latest/meta-data/iam/security-credentials/ (stealing cloud IAM tokens). The fetched content lands in the migrated repo's release assets, readable by the attacker.

The vendor rates this MEDIUM at CVSS 6.8 (AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N). That score correctly captures the PR:H (high-privilege) prerequisite and the Scope:Changed / Confidentiality:High impact. But the vendor label under-weights what Gitea *is*: a self-hosted Git forge that frequently serves as the CI/CD backbone (Gitea Actions), artifact store, and secret vault for development teams. When an SSRF on this component leaks app.ini, the attacker gets database credentials and JWT signing keys — enough to forge admin tokens and pivot to supply-chain compromise. The 6.8 is defensible in isolation; for the role Gitea actually plays, HIGH is the right bucket.

"Admin-gated SSRF reads app.ini secrets and cloud metadata — upgrade floors it to HIGH on CI/CD forges"
02 · The Attack Path

5 steps from start to impact.

STEP 01

Attain migration-capable account

The attacker needs an account on the target Gitea instance with repository migration permissions. By default this requires admin or org owner role. If open registration is enabled and the admin hasn't restricted migration to admins-only ([service].DISABLE_REGISTRATION = false, [migrations].ALLOWED_DOMAINS unconfigured), a newly registered user may have migration rights depending on instance policy.
Conditions required:
  • Valid account on target Gitea instance
  • Migration permission (admin or org owner by default)
Where this breaks in practice:
  • Requires PR:H — not accessible to anonymous or low-privilege users
  • Many production Gitea instances disable open registration
Detection/coverage: Auth logs show migration API calls; audit POST /repo/migrate or POST /api/v1/repos/migrate endpoints
STEP 02

Set up malicious migration source

The attacker stands up an external Gitea (or compatible forge) instance hosting a repository with release assets whose download URLs point to internal targets: file:///etc/gitea/app.ini, http://169.254.169.254/latest/meta-data/iam/security-credentials/, or http://10.0.0.1:8500/v1/kv/secrets. The migration protocol trusts these URLs as legitimate asset sources.
Conditions required:
  • Attacker-controlled external Gitea instance or compatible API
  • Knowledge of internal target URLs (cloud metadata is deterministic)
Where this breaks in practice:
  • Attacker must host infrastructure; not a single-packet exploit
  • Cloud metadata theft only works against IMDSv1 — IMDSv2 requires a PUT hop that raw GET cannot perform
Detection/coverage: Outbound HTTP requests from Gitea to RFC 1918 addresses or 169.254.169.254 should trigger egress monitoring alerts
STEP 03

Trigger migration via raw http.Get() path

The attacker initiates a repository migration from their malicious source. When Gitea processes release asset downloads and PR patch URLs, it calls uri.Open() or direct http.Get() in gitea_uploader.go and dump.go. These calls use Go's DefaultClient with no hostmatcher validation, so the request goes straight to the internal target. The file:// scheme handler in uri.Open() reads local files directly.
Conditions required:
  • Migration initiated by the privileged user
  • Target Gitea instance has network path to internal resources or local filesystem
Where this breaks in practice:
  • The migration must complete without timeout or error for the payload to land
  • File-system reads are limited to files readable by the Gitea process user
Detection/coverage: Network monitoring or WAF logging on the Gitea host for outbound requests to metadata IPs; file access auditing (auditd) on /etc/gitea/app.ini
STEP 04

Exfiltrate secrets from migrated assets

The fetched content (app.ini contents, cloud IAM credentials, internal API responses) is stored as release assets or PR patch data in the newly migrated repository. The attacker reads these through the Gitea web UI or API, obtaining database credentials, JWT signing secrets, SMTP passwords, or temporary cloud IAM role credentials.
Conditions required:
  • Migration completes successfully
  • Attacker has read access to the migrated repository
Where this breaks in practice:
  • Large responses may be truncated or cause migration errors
  • Cloud IAM tokens are temporary (typically 1-6 hours)
Detection/coverage: Unusual repository creation with release assets containing non-binary content; data-loss-prevention scanning on repository contents
STEP 05

Pivot to platform or cloud compromise

With app.ini secrets, the attacker forges JWT admin tokens or connects directly to the backing database, achieving full Gitea platform compromise. With cloud IAM credentials, they enumerate and access S3 buckets, EC2 instances, RDS databases, or other cloud resources attached to the Gitea instance's IAM role. From full Gitea admin, they can inject malicious commits, alter CI/CD pipelines, and poison downstream builds.
Conditions required:
  • Exfiltrated credentials are valid and unexpired
  • Target infrastructure uses the compromised credentials without additional auth barriers (MFA, IP allowlisting)
Where this breaks in practice:
  • Well-configured cloud environments use IMDSv2, VPC endpoints, and role scoping that limit lateral movement
  • Database access may require network-level access the attacker doesn't have
Detection/coverage: Cloud audit trails (CloudTrail, GCP Audit Logs) for credential use from unexpected IPs; Gitea admin action logs for unexpected token issuance
03 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNo known active exploitation. Not listed on CISA KEV. No reports of campaigns targeting this specific CVE as of 2026-08-12.
Proof of ConceptConceptual PoC described in the advisory and Kodem analysis — crafting a malicious migration source with release assets pointing to file:// or metadata URLs. No weaponized public exploit tool identified. The attack is straightforward to reproduce for anyone with admin access.
EPSS ScoreNot yet scored — CVE disclosed 2026-07-21, EPSS data may lag by several weeks for new CVEs.
KEV StatusNot listed. No CISA KEV entry as of 2026-08-12.
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N6.8 MEDIUM. Key: network-accessible, low complexity, but requires high privileges. Scope is Changed (impact extends beyond the vulnerable component). Confidentiality impact is High.
Affected VersionsAll Gitea versions < 1.27.0. The vulnerable code paths exist in modules/uri/uri.go, services/migrations/gitea_uploader.go, services/migrations/dump.go, and routers/web/auth/oauth.go.
Fixed VersionGitea 1.27.0 (released 2026-07-22). Fix applied via PR #38406. No backport to 1.26.x branch has been announced.
Exposure Data~34,000 internet-facing Gitea instances per Shodan scans (SecurityWeek, May 2026). Separate Docker-specific scan found ~6,200 instances. Many enterprises run Gitea internally behind VPN/firewall.
Disclosure Date2026-07-21 (MITRE CNA submission). Gitea 1.27.0 released 2026-07-22.
ReportersMultiple researchers: @tikket1, @theluckystrike, @Letian-aarch64, @JebeenLee, @JLLeitschuh, @pick, @noobx123 — coordinated disclosure via Gitea security program.
04 · The Call

noisgate verdict.

Final Verdict
UPGRADED to HIGH (7.5/10)

Role multiplier — Gitea is canonically a source-code forge, CI/CD pipeline hub, and secret store; SSRF-to-credential-theft on this component ends in platform takeover and supply-chain pivot, flooring the verdict at HIGH. The PR:H requirement (admin or org owner) prevents escalation to CRITICAL, as the attacker population is narrowed to a small set of privileged insiders or accounts obtained through prior compromise.

HIGH Vulnerability existence and affected code paths
HIGH Attack prerequisites (PR:H confirmed by CVSS vector and advisory)
MEDIUM Blast radius in CI/CD deployments (depends on instance configuration and cloud environment)
LOW Exploitation in the wild (no evidence yet, but attack is simple to execute)

Why this verdict

  • PR:H narrows attacker population: The CVSS vector confirms high-privilege access is required. Default Gitea configuration restricts migration to admins and org owners — this is not an unauthenticated or low-privilege bug, which limits mass exploitation.
  • Scope:Changed multiplies impact beyond the host: The SSRF reads files and services outside Gitea's own authorization boundary — cloud IAM credentials, database connection strings, and JWT signing secrets in app.ini. This is not a contained information disclosure.
  • Role multiplier — CI/CD forge is a high-value target by definition: Gitea sits in the CI/CD + artifact stores category. ≥10% of Gitea installations serve as the primary development platform with Actions runners, making them the CI/CD backbone. Credential theft from app.ini yields database access and admin token forgery → full platform compromise → malicious commit injection → supply-chain pivot. This floors the verdict at HIGH per the deployment-role blast radius rule.
  • ~34,000 internet-facing instances provide attack surface: Shodan data confirms significant external exposure. While many enterprise Gitea instances are internal-only, the external population is non-trivial and each is a potential target if the attacker can obtain or already holds an admin account.
  • IMDSv2 adoption limits cloud metadata theft: AWS environments enforcing IMDSv2 are immune to the metadata theft vector (raw GET cannot complete the PUT token hop). This friction applies to the cloud-pivot path specifically but does not reduce the local file-read (file:///etc/gitea/app.ini) vector.

Why not higher?

CRITICAL would require either unauthenticated access or active exploitation. This CVE demands PR:H (admin/org owner), which means the attacker is either a malicious insider or has already compromised a high-privilege account through a separate chain. The bug amplifies an existing privileged position rather than granting initial access. No KEV listing and no observed campaigns further weigh against CRITICAL.

Why not lower?

MEDIUM ignores the deployment-role reality. Gitea is not a peripheral tool — it is the source-code repository, CI/CD engine, and often the secret store for development organizations. The SSRF reads app.ini containing database credentials and JWT signing secrets, which is a direct path to full platform takeover. The Scope:Changed designation in the CVSS vector confirms impact extends beyond the component. Downgrading past HIGH would require evidence that <1% of Gitea installations occupy the CI/CD high-value role, which contradicts Gitea's entire market position.

05 · Compensating Control

What to do — in priority order.

  1. Enforce IMDSv2 on all cloud instances running Gitea — IMDSv2 requires a PUT request to obtain a session token before metadata is accessible. The raw http.Get() in the vulnerable code paths cannot complete this handshake, fully neutralizing the cloud metadata theft vector. Deploy within the noisgate mitigation SLA of 30 days for HIGH.
  2. Restrict migration permissions to a dedicated admin account — Set [service].ENABLE_NOTIFY_MAIL = false and review [migrations] config. Ensure only a single, tightly-controlled admin account can perform migrations. This shrinks the attacker population to near-zero for external threat actors. Implement within 30 days.
  3. Block outbound requests from Gitea to RFC 1918 and link-local ranges — Configure egress firewall rules (iptables, security groups, or network policy) on the Gitea host to deny outbound connections to 169.254.169.254, 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 from the Gitea process. This breaks both the cloud metadata and internal network scanning vectors at the network layer.
  4. Disable open registration if not required — Open registration with default migration permissions gives external attackers a path to PR:H. Set [service].DISABLE_REGISTRATION = true or REGISTER_MANUAL_CONFIRM = true to prevent self-service account creation. This eliminates the external attacker's easiest path to the required privilege level.
  5. Rotate secrets in app.ini post-patch — After upgrading to 1.27.0, rotate the JWT_SECRET, INTERNAL_TOKEN, SECRET_KEY, database password, and any SMTP credentials stored in app.ini. If the SSRF was exploited before patching, these secrets are compromised. Treat this as a mandatory post-remediation step.
What doesn't work
  • WAF rules on inbound requests — the SSRF is triggered by Gitea's own outbound HTTP client during migration, not by a malicious inbound payload. A WAF in front of Gitea cannot inspect or block the server-side request.
  • Gitea's built-in hostmatcher allowlist/blocklist — this is precisely what the vulnerability bypasses. The three affected code paths do not use hostmatcher.NewDialContext(), so configuring [migrations].ALLOWED_DOMAINS or BLOCKED_DOMAINS has no effect on these specific paths until you upgrade to 1.27.0.
  • Network segmentation alone (without egress filtering) — segmenting Gitea into its own VLAN helps, but if the Gitea host can still reach its own localhost or the cloud metadata endpoint, the most damaging vectors remain viable.
06 · Verification

Crowdsourced verification payload.

Run this script on the Gitea host (or any host where you can query the Gitea API) as any user. It checks the Gitea version via the API. Usage: bash check_cve_2026_59765.sh https://gitea.example.com. No special privileges required — uses the public /api/v1/version endpoint.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_59765.sh — Detect CVE-2026-59765 (Gitea SSRF via migration asset downloads)
# Usage: bash check_cve_2026_59765.sh <GITEA_BASE_URL>
# Exit codes: 1 = VULNERABLE, 0 = PATCHED, 2 = UNKNOWN

set -euo pipefail

if [ $# -lt 1 ]; then
  echo "Usage: $0 <GITEA_BASE_URL>"
  echo "Example: $0 https://gitea.example.com"
  exit 2
fi

BASE_URL="${1%/}"
API_URL="${BASE_URL}/api/v1/version"

# Fetch version
RESPONSE=$(curl -sf --max-time 10 "$API_URL" 2>/dev/null) || {
  echo "UNKNOWN — Could not reach Gitea API at $API_URL"
  exit 2
}

VERSION=$(echo "$RESPONSE" | grep -oP '"version"\s*:\s*"\K[^"]+' 2>/dev/null) || {
  echo "UNKNOWN — Could not parse version from API response"
  exit 2
}

echo "Detected Gitea version: $VERSION"

# Compare version — vulnerable if < 1.27.0
# Extract major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$(echo "$VERSION" | grep -oP '^[0-9]+\.[0-9]+\.[0-9]+')"

if [ -z "$MAJOR" ] || [ -z "$MINOR" ]; then
  echo "UNKNOWN — Could not parse version components from '$VERSION'"
  exit 2
fi

PATCH=${PATCH:-0}

if [ "$MAJOR" -lt 1 ]; then
  echo "VULNERABLE — Gitea $VERSION is affected by CVE-2026-59765 (fixed in 1.27.0)"
  exit 1
elif [ "$MAJOR" -eq 1 ] && [ "$MINOR" -lt 27 ]; then
  echo "VULNERABLE — Gitea $VERSION is affected by CVE-2026-59765 (fixed in 1.27.0)"
  exit 1
else
  echo "PATCHED — Gitea $VERSION includes the fix for CVE-2026-59765"
  exit 0
fi
07 · Bottom Line

If you remember one thing.

TL;DR
Upgrade all Gitea instances to 1.27.0 immediately. This CVE is assessed as HIGH (7.5) by noisgate, upgraded from the vendor's MEDIUM 6.8, because Gitea is a CI/CD-class component where SSRF-to-credential-theft chains into full platform compromise and supply-chain risk. Under the noisgate mitigation SLA, deploy compensating controls (IMDSv2 enforcement, egress filtering to block metadata and RFC 1918 from Gitea, restrict migration permissions to a single locked-down admin) within 30 days. Under the noisgate remediation SLA, apply the 1.27.0 patch across all instances within 180 days — but given the simplicity of exploitation for any admin-level user and the lack of a 1.26.x backport, front-load the upgrade into your next maintenance window. Post-upgrade, rotate all secrets in app.ini (JWT_SECRET, INTERNAL_TOKEN, SECRET_KEY, database and SMTP credentials) as a precaution. No active exploitation is known, but the attack requires zero tooling beyond a crafted migration source — do not wait for a weaponized exploit to appear.

Sources

  1. GitLab Advisory Database — CVE-2026-59765
  2. Kodem — CVE-2026-59765 Analysis
  3. Gitea Blog — Release of 1.27.0
  4. GitHub Advisory — GHSA-2wm4-vwp6-v7xc
  5. SecurityWeek — Gitea Vulnerability Exposed 30,000 Deployments
  6. Miggo Vulnerability Database — CVE-2026-59765
  7. Hive Security — Gitea/Forgejo CVE Analysis
  8. AlternativeTo — Gitea 1.27.0 Security Fixes
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.