Executive Summary
A critical Server-Side Request Forgery (SSRF) vulnerability in Gitea allows authenticated users to reach internal network services that should be blocked by Gitea's allow-list filtering. The root cause is the use of Go's net.IP.IsPrivate() function, which only recognizes RFC 1918 private ranges — leaving entire categories of sensitive internal IP space completely unprotected.
CVSS Score: 9.6 (Critical)
Attack Vector: Network | Low Privilege Required | No User Interaction
In cloud deployments, the most severe impact is exposure of AWS Instance Metadata Service (IMDS) credentials via IPv6 NAT64 tunneling, potentially granting attackers full cloud account access. Fixed in Gitea 1.26.3.
Vulnerability Overview
Root Cause
Gitea's SSRF protection in webhooks and repository migration uses net.IP.IsPrivate() (via HostMatchList.checkIP at line 103) to determine whether an outbound connection is to a private address. This function only blocks:
10.0.0.0/8172.16.0.0/12192.168.0.0/16fc00::/7(IPv6 ULA)::1/128(IPv6 loopback)
Entire classes of sensitive IP space fall through unblocked.
Exploitable Bypass Ranges
| Range | RFC | Impact |
|---|---|---|
100.64.0.0/10 | RFC 6598 (CGNAT) | Internal services in cloud/container envs |
168.63.129.16/32 | Azure WireServer | Azure managed identity tokens |
172.32.0.0/11 | Non-RFC1918 range | Internal services |
64:ff9b::/96 | RFC 6052 NAT64 | Embeds 169.254.169.254 → AWS IMDS |
2001::/32 | Teredo | IPv6 tunnel bypass |
2002::/16 | 6to4 | IPv6 tunnel bypass |
Technical Details
AWS IMDS via NAT64 — Most Dangerous Vector
The NAT64 prefix 64:ff9b::/96 maps IPv4 addresses into IPv6 space. The AWS Instance Metadata Service address 169.254.169.254 (link-local, which is blocked) translates to:
64:ff9b::a9fe:a9fe (NAT64 representation of 169.254.169.254)
Because 64:ff9b::/96 is a global unicast range, net.IP.IsPrivate() returns false — the connection is allowed. An attacker who configures a webhook or migration URL targeting this address can read IMDS responses directly from Gitea's webhook history UI (up to 1 MiB).
Webhook URL → http://[64:ff9b::a9fe:a9fe]/latest/meta-data/iam/security-credentials/
Response → {"AccessKeyId":"...","SecretAccessKey":"...","Token":"..."}CVE Classification
| Field | Value |
|---|---|
| CVE ID | CVE-2026-22874 |
| CVSS 3.1 Score | 9.6 Critical |
| Vector | AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| CWE | CWE-918 (Server-Side Request Forgery) |
| Reported by | @JLLeitschuh |
| Fixed in | Gitea PRs #38059, #38173 → v1.26.3 |
Why the Response Is Visible
Gitea's webhook history UI renders the full HTTP response body for debugging. An attacker simply:
- Adds a webhook pointing to an internal SSRF target
- Triggers any repo event to fire the webhook
- Opens the webhook delivery history in the UI
- Reads up to 1 MiB of the internal response — no exfiltration tooling needed
Attack Chain
1. Attacker has any Gitea account (even low-privilege)
2. Creates webhook or initiates repo migration to bypass target
3. Points URL at CGNAT/NAT64/Azure bypass address
4. Gitea's filter passes the request (IsPrivate() returns false)
5. Gitea fetches internal resource and stores full response
6. Attacker reads cloud credentials from webhook delivery history
7. Uses stolen IAM credentials to pivot to cloud infrastructureIdentifying Affected Deployments
All Gitea installations through version 1.26.2 are affected regardless of installation method (Docker, binary, package manager).
High-Risk Configurations
| Environment | Risk Level |
|---|---|
| Gitea on AWS EC2 / ECS / Lambda | Critical — IMDS credential theft |
| Gitea on Azure VM / ACI | Critical — managed identity token theft |
| Gitea on GCP (with NAT64) | High — metadata service exposure |
| Gitea with CGNAT internal network | High — internal service access |
| Gitea with public-facing webhooks enabled | High |
| Air-gapped Gitea, no external network | Lower |
Immediate Remediation
Option 1: Upgrade to Gitea 1.26.3 (Recommended)
The patch replaces net.IP.IsPrivate() with a comprehensive deny-list approach modeled on CC-Tweaked's AddressPredicate, explicitly blocking all dangerous ranges including CGNAT, NAT64 tunnels, and cloud metadata addresses.
# Docker
docker pull gitea/gitea:1.26.3
docker-compose up -d
# Binary — download from releases.gitea.ioOption 2: Network-Level Mitigation
Block outbound connections from the Gitea server to sensitive ranges at the network/firewall level:
# Block CGNAT range from Gitea container
iptables -I OUTPUT -s <gitea-container-ip> -d 100.64.0.0/10 -j DROP
# Block Azure WireServer
iptables -I OUTPUT -s <gitea-container-ip> -d 168.63.129.16 -j DROP
# Block link-local (should already be blocked, verify)
iptables -I OUTPUT -s <gitea-container-ip> -d 169.254.0.0/16 -j DROPOption 3: Restrict Webhook Creation
Limit who can create webhooks to trusted administrators only:
# app.ini
[webhook]
ALLOWED_HOST_LIST = external ; or specify explicit allowed domainsDetection
Look for Suspicious Webhook Targets
-- Query Gitea database for webhooks targeting internal ranges
SELECT w.url, w.repo_id, u.name
FROM webhook w
JOIN user u ON w.repo_id = u.id
WHERE w.url LIKE '%100.64.%'
OR w.url LIKE '%168.63.129.%'
OR w.url LIKE '%64:ff9b:%'
OR w.url LIKE '%169.254.%';Check Webhook Delivery History
# Review recent webhook deliveries for internal IP responses
docker exec <gitea-db> psql -U gitea -c \
"SELECT request_url, response_content FROM hook_task
WHERE created > NOW() - INTERVAL '30 days'
AND (request_url LIKE '%100.64%' OR request_url LIKE '%64:ff9b%')"